/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.components.parametricstudy.execution; import java.io.Serializable; import java.util.HashMap; import java.util.Map; import java.util.NoSuchElementException; import de.rcenvironment.components.parametricstudy.common.Dimension; import de.rcenvironment.components.parametricstudy.common.Measure; import de.rcenvironment.components.parametricstudy.common.ParametricStudyComponentConstants; import de.rcenvironment.components.parametricstudy.common.ParametricStudyService; import de.rcenvironment.components.parametricstudy.common.StudyDataset; import de.rcenvironment.components.parametricstudy.common.StudyPublisher; import de.rcenvironment.components.parametricstudy.common.StudyStructure; import de.rcenvironment.core.component.api.ComponentException; import de.rcenvironment.core.component.execution.api.ComponentContext; import de.rcenvironment.core.component.execution.api.ThreadHandler; import de.rcenvironment.core.component.model.api.LazyDisposal; import de.rcenvironment.core.component.model.spi.AbstractNestedLoopComponent; import de.rcenvironment.core.datamodel.api.TypedDatum; import de.rcenvironment.core.datamodel.types.api.FloatTD; import de.rcenvironment.core.datamodel.types.api.IntegerTD; import de.rcenvironment.core.utils.common.StringUtils; /** * Parametric Study component implementation. * * @author Markus Kunde * @author Arne Bachmann * @author Doreen Seider */ @LazyDisposal public class ParametricStudyComponent extends AbstractNestedLoopComponent { private static final String TRUE = "true"; private static final String GET_STUDYPARAMETERS_STRING = "Not-a-value at '%s' Input."; private static final int MINUS_ONE = -1; private static ParametricStudyService parametricStudyService; private StudyPublisher study; private double from; private double to; private double stepSize; private double designVariable; private long steps; private boolean fitStepSizeToBounds = true; private int stepCount = 1; private boolean isDone; private volatile boolean canceled = false; private static StudyStructure createStructure(final ComponentContext compExeCtx) { final StudyStructure structure = new StudyStructure(); // outputs are dimensions for (String outputName : compExeCtx.getOutputs()) { if (outputName.equals(ParametricStudyComponentConstants.OUTPUT_NAME_DV)) { final Dimension dimension = new Dimension(outputName, true); structure.addDimension(dimension); } } // inputs are measures for (String inputName : compExeCtx.getInputs()) { final Measure measure = new Measure(inputName); structure.addMeasure(measure); } return structure; } @Override public boolean treatStartAsComponentRun() { return !hasForwardingStartInputs() && !hasStudyParameterInputs(); } @Override public void startNestedComponentSpecific() throws ComponentException { parametricStudyService = componentContext.getService(ParametricStudyService.class); if (treatStartAsComponentRun()) { setStudyParameters(); checkStepSize(); initilizeStudy(); sendDesignVariableToOutput(calculateInitialDesignVariable()); if (!hasEvaluationResultFromLoopInput()) { runFullStudyAtOnce(); } } else { if (isInInnerLoop()) { if (!hasEvaluationResultFromLoopInput() && !hasForwardingStartInputs()) { throw new ComponentException("Component is in a nested loop and" + " thus needs at least one 'foward' or 'evaluation result' input to control the flow of the inner loop."); } } setComponentDone(false); } } @Override public void processInputsNestedComponentSpecific() throws ComponentException { if (stepCount == 1) { // this is only the case if any of the study parameters is set via an Input setStudyParameters(); checkStepSize(); initilizeStudy(); if (!hasEvaluationResultFromLoopInput() && !hasForwardingStartInputs()) { runFullStudyAtOnce(); } } if (componentContext.isOutputClosed(ParametricStudyComponentConstants.OUTPUT_NAME_DV)) { throw new ComponentException(StringUtils.format("Too many values received. " + "Expect exactly one value per input per design variable sent. " + "%s design variables(s) sent and %s value(s) received", steps, steps + 1)); } // send input parameters to study service for monitoring purposes final Map<String, Serializable> values = new HashMap<String, Serializable>(); // input parameters are response to previous iteration if (fitStepSizeToBounds) { values.put(ParametricStudyComponentConstants.OUTPUT_NAME_DV, getLastDesignVariableFittingStepSizeToBounds()); } else { values.put(ParametricStudyComponentConstants.OUTPUT_NAME_DV, getLastDesignVariableNotFittingStepSizeToBounds()); } for (String inputName : componentContext.getInputsWithDatum()) { if (componentContext.isDynamicInput(inputName)) { TypedDatum input = componentContext.readInput(inputName); switch (input.getDataType()) { case NotAValue: values.put(inputName, Double.NaN); break; case Integer: values.put(inputName, ((IntegerTD) input).getIntValue()); break; case Float: values.put(inputName, ((FloatTD) input).getFloatValue()); break; default: values.put(inputName, "Not applicable"); } } } study.add(new StudyDataset(values)); setComponentDone(allDesignVariablesSent()); } @Override protected void sendValuesNestedComponentSpecific() { if (fitStepSizeToBounds) { sendDesignVariableToOutput(calculateDesignVariableFittingStepSizeToBounds(stepCount)); } else { sendDesignVariableToOutput(calculateDesignVariableNotFittingStepSizeToBounds()); } } @Override public void onStartInterrupted(ThreadHandler executingThreadHandler) { canceled = true; } private void runFullStudyAtOnce() throws ComponentException { // send initial variable if (fitStepSizeToBounds) { for (int step = stepCount; step <= steps; step++) { sendDesignVariableToOutput(calculateDesignVariableFittingStepSizeToBounds(step)); if (canceled) { break; } } } else { double nextDesignVariable = calculateDesignVariableNotFittingStepSizeToBounds(); while (designVariableIsInBounds(nextDesignVariable)) { sendDesignVariableToOutput(calculateDesignVariableNotFittingStepSizeToBounds()); nextDesignVariable = calculateDesignVariableNotFittingStepSizeToBounds(); if (canceled) { break; } } } setComponentDone(true); } @Override public void dispose() { if (study != null) { study.clearStudy(); } } @Override protected boolean isDoneNestedComponentSpecific() { return isDone; } @Override protected void resetNestedComponentSpecific() { stepCount = 1; setComponentDone(false); } @Override protected void finishLoopNestedComponentSpecific() {} @Override protected void sendFinalValues() throws ComponentException {} private void sendDesignVariableToOutput(double value) { writeOutput(ParametricStudyComponentConstants.OUTPUT_NAME_DV, typedDatumFactory.createFloat(value)); componentLog.componentInfo(StringUtils.format("Wrote to output '%s': %s", ParametricStudyComponentConstants.OUTPUT_NAME_DV, value)); stepCount++; } private double calculateInitialDesignVariable() { if (fitStepSizeToBounds) { return calculateInitialDesignVariableFittingStepSizeToBounds(); } else { return calculateInitialDesignVariableNotFittingStepSizeToBounds(); } } private double calculateInitialDesignVariableFittingStepSizeToBounds() { designVariable = from; return designVariable; } private double calculateDesignVariableFittingStepSizeToBounds(int step) { // cover if there is only one step to be done (division by zero) if (step == 1) { return from; } else { designVariable = from + (to - from) * (step - 1.0) / (steps - 1.0); return designVariable; } } private double getLastDesignVariableFittingStepSizeToBounds() { return designVariable; } private double calculateInitialDesignVariableNotFittingStepSizeToBounds() { return from; } private double calculateDesignVariableNotFittingStepSizeToBounds() { return from + (stepCount - 1) * stepSize; } private double getLastDesignVariableNotFittingStepSizeToBounds() { return from + (stepCount - 1) * stepSize; } private boolean allDesignVariablesSent() { if (fitStepSizeToBounds) { if (stepCount <= steps) { return false; } } else { if (designVariableIsInBounds(calculateDesignVariableNotFittingStepSizeToBounds())) { return false; } } return true; } private void setComponentDone(boolean done) { isDone = done; } private boolean hasStudyParameterInputs() { for (String inputName : componentContext.getInputs()) { if (componentContext.getDynamicInputIdentifier(inputName) .equals(ParametricStudyComponentConstants.DYNAMIC_INPUT_STUDY_PARAMETERS)){ return true; } } return false; } private boolean hasEvaluationResultFromLoopInput() { for (String inputName : componentContext.getInputs()) { if (componentContext.getDynamicInputIdentifier(inputName) .equals(ParametricStudyComponentConstants.DYNAMIC_INPUT_IDENTIFIER)){ return true; } } return false; } private void setStudyParameters() throws ComponentException { try { if (componentContext.getOutputMetaDataValue(ParametricStudyComponentConstants.OUTPUT_NAME_DV, ParametricStudyComponentConstants.OUTPUT_METADATA_USE_INPUT_AS_FROM_VALUE).equals(TRUE)) { TypedDatum input = componentContext.readInput(ParametricStudyComponentConstants.INPUT_NAME_FROM_VALUE); double value; switch (input.getDataType()) { case NotAValue: throw new ComponentException(StringUtils.format(GET_STUDYPARAMETERS_STRING, ParametricStudyComponentConstants.INPUT_NAME_FROM_VALUE)); case Integer: value = (double) ((IntegerTD) input).getIntValue(); break; case Float: value = (double) ((FloatTD) input).getFloatValue(); break; default: value = 0; } from = value; } else { from = Double.valueOf(componentContext.getOutputMetaDataValue(ParametricStudyComponentConstants.OUTPUT_NAME_DV, ParametricStudyComponentConstants.OUTPUT_METATDATA_FROMVALUE)); } if (componentContext.getOutputMetaDataValue(ParametricStudyComponentConstants.OUTPUT_NAME_DV, ParametricStudyComponentConstants.OUTPUT_METADATA_USE_INPUT_AS_TO_VALUE).equals(TRUE)) { TypedDatum input = componentContext.readInput(ParametricStudyComponentConstants.INPUT_NAME_TO_VALUE); double value; switch (input.getDataType()) { case NotAValue: throw new ComponentException(StringUtils.format(GET_STUDYPARAMETERS_STRING, ParametricStudyComponentConstants.INPUT_NAME_TO_VALUE)); case Integer: value = (double) ((IntegerTD) input).getIntValue(); break; case Float: value = (double) ((FloatTD) input).getFloatValue(); break; default: value = 0; } to = value; } else { to = Double.valueOf(componentContext.getOutputMetaDataValue(ParametricStudyComponentConstants.OUTPUT_NAME_DV, ParametricStudyComponentConstants.OUTPUT_METATDATA_TOVALUE)); } if (componentContext.getOutputMetaDataValue(ParametricStudyComponentConstants.OUTPUT_NAME_DV, ParametricStudyComponentConstants.OUTPUT_METADATA_USE_INPUT_AS_STEPSIZE_VALUE).equals(TRUE)) { TypedDatum input = componentContext.readInput(ParametricStudyComponentConstants.INPUT_NAME_STEPSIZE_VALUE); double value; switch (input.getDataType()) { case NotAValue: throw new ComponentException(StringUtils.format(GET_STUDYPARAMETERS_STRING, ParametricStudyComponentConstants.INPUT_NAME_STEPSIZE_VALUE)); case Integer: value = (double) ((IntegerTD) input).getIntValue(); break; case Float: value = (double) ((FloatTD) input).getFloatValue(); break; default: value = 0; } stepSize = value; } else { stepSize = Double.valueOf(componentContext.getOutputMetaDataValue(ParametricStudyComponentConstants.OUTPUT_NAME_DV, ParametricStudyComponentConstants.OUTPUT_METATDATA_STEPSIZE)); } } catch (NoSuchElementException e) { throw new ComponentException("Expected Input not connected.", e.getCause()); } } private void checkStepSize() throws ComponentException { if (stepSize <= 0) { throw new ComponentException(StringUtils.format("Invalid step size: %f , must be > 0", stepSize)); } if (from > to) { stepSize = stepSize * MINUS_ONE; } } private void initilizeStudy() throws ComponentException { study = parametricStudyService.createPublisher( componentContext.getExecutionIdentifier(), componentContext.getInstanceName(), createStructure(componentContext)); double stepsDouble = ((Double) Math.floor((to - from) / stepSize)) + 1; // including first AND last if (stepsDouble >= Long.MAX_VALUE){ throw new ComponentException("The number of values produced by the Component exceeds the numerical limits."); } steps = (long) stepsDouble; componentLog.componentInfo(StringUtils.format("Sampling from %s to %s with step size %s -> %d value(s)", from, to, stepSize, steps)); if (!hasForwardingStartInputs() && !hasEvaluationResultFromLoopInput()) { componentLog.componentInfo("No 'forwarding' or 'evaluation result' inputs defined -> writing all values at once"); } if (componentContext.getOutputMetaDataValue(ParametricStudyComponentConstants.OUTPUT_NAME_DV, ParametricStudyComponentConstants.OUTPUT_METATDATA_FIT_STEP_SIZE_TO_BOUNDS) != null) { fitStepSizeToBounds = Boolean.valueOf(componentContext.getOutputMetaDataValue(ParametricStudyComponentConstants.OUTPUT_NAME_DV, ParametricStudyComponentConstants.OUTPUT_METATDATA_FIT_STEP_SIZE_TO_BOUNDS)); } } private boolean designVariableIsInBounds(double designVariableToTest) { if (stepSize < 0) { return designVariableToTest <= from && designVariableToTest >= to; } else { return designVariableToTest >= from && designVariableToTest <= to; } } }