/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.components.converger.execution; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import org.apache.commons.collections4.queue.CircularFifoQueue; import de.rcenvironment.components.converger.common.ConvergerComponentConstants; import de.rcenvironment.core.component.api.ComponentException; import de.rcenvironment.core.component.api.LoopComponentConstants; import de.rcenvironment.core.component.model.spi.AbstractNestedLoopComponent; import de.rcenvironment.core.datamodel.api.DataType; import de.rcenvironment.core.datamodel.api.TypedDatumFactory; import de.rcenvironment.core.datamodel.api.TypedDatumService; import de.rcenvironment.core.datamodel.types.api.FloatTD; import de.rcenvironment.core.datamodel.types.api.IntegerTD; import de.rcenvironment.core.utils.common.StringUtils; /** * Component to get data to convergence. * * @author Sascha Zur * @author Doreen Seider */ public class ConvergerComponent extends AbstractNestedLoopComponent { private static final int NO_MAX_CONVERGENCE_CHECKS = -1; private Map<String, CircularFifoQueue<Double>> valueTuples; private double epsA; private double epsR; private int valueTuplesToConsider; private int valueTuplesProcessed = 0; private int maxConvergenceChecks = NO_MAX_CONVERGENCE_CHECKS; private int convergenceChecks = 0; private NotConvergedBehavior notConvergedBehavior; /** * @author Sascha Zur on behalf of Caslav Ilic */ private enum NotConvergedBehavior { Ignore, Fail, NotAValue; } private Boolean[] isConverged; private Map<String, Boolean[]> isSingleConverged; private NotConvergedBehavior getNotConvergedBehavior() { if (Boolean.valueOf(componentContext.getConfigurationValue(ConvergerComponentConstants.NOT_CONVERGED_FAIL))) { return NotConvergedBehavior.Fail; } else if (Boolean.valueOf(componentContext.getConfigurationValue(ConvergerComponentConstants.NOT_CONVERGED_NOT_A_VALUE))) { return NotConvergedBehavior.NotAValue; } else { return NotConvergedBehavior.Ignore; } } @Override public void startNestedComponentSpecific() throws ComponentException { epsA = Double.parseDouble(componentContext.getConfigurationValue(ConvergerComponentConstants.KEY_EPS_A)); epsR = Double.parseDouble(componentContext.getConfigurationValue(ConvergerComponentConstants.KEY_EPS_R)); String iterationsToConsiderAsString = componentContext.getConfigurationValue(ConvergerComponentConstants.KEY_ITERATIONS_TO_CONSIDER); valueTuplesToConsider = Integer.parseInt(iterationsToConsiderAsString) + 1; // values of // last + tuples // of current // iteration(s) String maxConvergenceChecksAsString = componentContext.getConfigurationValue(ConvergerComponentConstants.KEY_MAX_CONV_CHECKS); if (maxConvergenceChecksAsString != null && !maxConvergenceChecksAsString.isEmpty()) { maxConvergenceChecks = Integer.parseInt(maxConvergenceChecksAsString); } initializeIterationValues(); isConverged = new Boolean[2]; isConverged[0] = false; isConverged[1] = false; isSingleConverged = new HashMap<>(); for (String inputName : componentContext.getInputs()) { Boolean[] isInputConverged = new Boolean[2]; isInputConverged[0] = false; isInputConverged[1] = false; isSingleConverged.put(inputName, isInputConverged); } notConvergedBehavior = getNotConvergedBehavior(); if (!hasStartValueInputs()) { sendValuesNestedComponentSpecific(); } } @Override public boolean treatStartAsComponentRun() { return !hasStartValueInputs(); } private boolean hasStartValueInputs() { for (String input : componentContext.getInputs()) { if (input.endsWith(LoopComponentConstants.ENDPOINT_STARTVALUE_SUFFIX)) { return true; } } return false; } private void initializeIterationValues() { valueTuples = new HashMap<String, CircularFifoQueue<Double>>(); for (String inputName : componentContext.getInputs()) { if (componentContext.isDynamicInput(inputName) && componentContext.getDynamicInputIdentifier(inputName).equals(ConvergerComponentConstants.ENDPOINT_ID_TO_CONVERGE) && !inputName.endsWith(LoopComponentConstants.ENDPOINT_STARTVALUE_SUFFIX)) { valueTuples.put(inputName, new CircularFifoQueue<Double>(valueTuplesToConsider)); } if (!inputName.endsWith(LoopComponentConstants.ENDPOINT_STARTVALUE_SUFFIX)) { if (Boolean.parseBoolean(componentContext.getInputMetaDataValue(inputName, ConvergerComponentConstants.META_HAS_STARTVALUE))) { valueTuples.get(inputName).add(Double.parseDouble(componentContext.getInputMetaDataValue( inputName, ConvergerComponentConstants.META_STARTVALUE))); } } } } private void addValuesToLastIterationsValues(boolean startValues) { for (String inputName : valueTuples.keySet()) { if (startValues && componentContext.getInputs().contains(inputName + LoopComponentConstants.ENDPOINT_STARTVALUE_SUFFIX)) { valueTuples.get(inputName).add( ((FloatTD) componentContext.readInput(inputName + LoopComponentConstants.ENDPOINT_STARTVALUE_SUFFIX)).getFloatValue()); } else if (!startValues) { if (componentContext.getInputDataType(inputName) == DataType.Float) { valueTuples.get(inputName).add(((FloatTD) componentContext.readInput(inputName)).getFloatValue()); } else if (componentContext.getInputDataType(inputName) == DataType.Integer) { valueTuples.get(inputName).add((double) ((IntegerTD) componentContext.readInput(inputName)).getIntValue()); } } } } private boolean areMaxConvergenceChecksReached() { return maxConvergenceChecks != NO_MAX_CONVERGENCE_CHECKS && convergenceChecks >= maxConvergenceChecks; } private Map<String, Boolean[]> isSingleConverged() { boolean convergenceCheckSkipped = false; Map<String, Boolean[]> isInputConverged = new HashMap<>(); for (String inputName : valueTuples.keySet()) { CircularFifoQueue<Double> values = valueTuples.get(inputName); int valueCount = values.size(); int[] range = getValueTupleRange(); boolean isCurrentConvergedAbs; boolean isCurrentConvergedRel; if (valueCount >= valueTuplesToConsider) { double maxValue = Collections.max(values); double minValue = Collections.min(values); isCurrentConvergedAbs = (Math.abs(maxValue - minValue) <= epsA); isCurrentConvergedRel = (Math.abs((maxValue - minValue) / maxValue) <= epsR); componentLog.componentInfo(StringUtils.format("%s [%d->%d] -> min: %s; max: %s; conv abs: %s; " + "conv rel: %s; #conv check: %d", inputName, range[0], range[1], minValue, maxValue, isCurrentConvergedAbs, isCurrentConvergedRel, convergenceChecks + 1)); } else { isCurrentConvergedAbs = false; isCurrentConvergedRel = false; convergenceCheckSkipped = true; componentLog.componentInfo( StringUtils.format("%s [%d->%d] -> skipped convergence check - not enough values yet (current: %s, required: %s)", inputName, range[0], range[1], valueCount, valueTuplesToConsider)); } Boolean[] isThisInputConverged = new Boolean[2]; isThisInputConverged[0] = isCurrentConvergedAbs; isThisInputConverged[1] = isCurrentConvergedRel; isInputConverged.put(inputName, isThisInputConverged); } if (!convergenceCheckSkipped) { convergenceChecks++; } return isInputConverged; } private Boolean[] isConverged(Map<String, Boolean[]> isInputConverged) { boolean isConvergedAbs = !valueTuples.isEmpty(); boolean isConvergedRel = !valueTuples.isEmpty(); for (String inputName : valueTuples.keySet()) { Boolean[] isThisInputConverged = isInputConverged.get(inputName); if (!isThisInputConverged[0]) { isConvergedAbs = false; } if (!isThisInputConverged[1]) { isConvergedRel = false; } } return new Boolean[] { isConvergedAbs, isConvergedRel }; } private int[] getValueTupleRange() { int rangeStart = valueTuplesProcessed - valueTuplesToConsider + 1; if (rangeStart < 0) { rangeStart = 0; } return new int[] { rangeStart, valueTuplesProcessed }; } @Override protected void sendValuesNestedComponentSpecific() { for (String inputName : valueTuples.keySet()) { if (!valueTuples.get(inputName).isEmpty()) { if (componentContext.getInputDataType(inputName) == DataType.Float) { writeOutput(inputName, typedDatumFactory.createFloat(valueTuples.get(inputName) .get(valueTuples.get(inputName).size() - 1))); } else if (componentContext.getInputDataType(inputName) == DataType.Integer) { double value = valueTuples.get(inputName) .get(valueTuples.get(inputName).size() - 1); writeOutput(inputName, typedDatumFactory.createInteger((long) value)); } writeOutput(inputName + ConvergerComponentConstants.IS_CONVERGED_OUTPUT_SUFFIX, typedDatumFactory.createBoolean( isSingleConverged.get(inputName)[0] || isSingleConverged.get(inputName)[1])); } } } private void forwardFinalValues() { Set<String> inputs = componentContext.getInputsWithDatum(); for (String input : inputs) { if (componentContext.getDynamicInputIdentifier(input).equals(LoopComponentConstants.ENDPOINT_ID_TO_FORWARD)) { writeOutput(input + ConvergerComponentConstants.CONVERGED_OUTPUT_SUFFIX, componentContext.readInput(input)); } } } private void sendConvergedValues() { if (notConvergedBehavior == NotConvergedBehavior.NotAValue && areMaxConvergenceChecksReached()) { TypedDatumFactory factory = componentContext.getService(TypedDatumService.class).getFactory(); componentContext.writeOutput(ConvergerComponentConstants.CONVERGED, factory.createNotAValue()); componentContext.writeOutput(ConvergerComponentConstants.CONVERGED_ABSOLUTE, factory.createNotAValue()); componentContext.writeOutput(ConvergerComponentConstants.CONVERGED_RELATIVE, factory.createNotAValue()); } else { writeOutput(ConvergerComponentConstants.CONVERGED, typedDatumFactory.createBoolean( isConverged[0] | isConverged[1])); writeOutput(ConvergerComponentConstants.CONVERGED_ABSOLUTE, typedDatumFactory.createBoolean(isConverged[0])); writeOutput(ConvergerComponentConstants.CONVERGED_RELATIVE, typedDatumFactory.createBoolean(isConverged[1])); } } @Override protected void resetNestedComponentSpecific() { convergenceChecks = 0; for (CircularFifoQueue<Double> queues : valueTuples.values()) { queues.clear(); } isConverged[0] = false; isConverged[1] = false; valueTuplesProcessed = 0; } @Override protected void finishLoopNestedComponentSpecific() { componentLog.componentInfo("Finished: converged abs: " + isConverged[0] + "; converged rel: " + isConverged[1] + "; reached max. conv checks: " + areMaxConvergenceChecksReached()); } @Override protected boolean isDoneNestedComponentSpecific() { return isConverged[0] || isConverged[1] || areMaxConvergenceChecksReached(); } @Override protected void processInputsNestedComponentSpecific() { addValuesToLastIterationsValues( componentContext.getInputsWithDatum().iterator().next().endsWith(LoopComponentConstants.ENDPOINT_STARTVALUE_SUFFIX)); isSingleConverged = isSingleConverged(); isConverged = isConverged(isSingleConverged); valueTuplesProcessed++; } @Override protected void sendFinalValues() throws ComponentException { boolean maxIterationsReached = areMaxConvergenceChecksReached(); TypedDatumFactory factory = componentContext.getService(TypedDatumService.class).getFactory(); if (notConvergedBehavior == NotConvergedBehavior.Fail && maxIterationsReached) { throw new ComponentException("Maximum number of checks reached without convergence."); } else if (notConvergedBehavior == NotConvergedBehavior.NotAValue && maxIterationsReached) { componentContext.getLog().componentError(StringUtils.format("Maximum number of checks (%d) reached without convergence", maxConvergenceChecks)); } for (String key : valueTuples.keySet()) { int valueCount = valueTuples.get(key).size(); if (valueCount > 0) { if (notConvergedBehavior == NotConvergedBehavior.NotAValue && maxIterationsReached) { componentContext.writeOutput(key + ConvergerComponentConstants.CONVERGED_OUTPUT_SUFFIX, factory.createNotAValue()); } else { if (componentContext.getOutputDataType(key).equals(DataType.Float)) { writeOutput(key + ConvergerComponentConstants.CONVERGED_OUTPUT_SUFFIX, typedDatumFactory.createFloat(valueTuples.get(key).get(valueCount - 1))); } else if (componentContext.getOutputDataType(key).equals(DataType.Integer)) { long value = (long) ((double) valueTuples.get(key).get(valueCount - 1)); writeOutput(key + ConvergerComponentConstants.CONVERGED_OUTPUT_SUFFIX, typedDatumFactory.createInteger(value)); } } } } forwardFinalValues(); sendConvergedValues(); } }