/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.components.doe.execution;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jackson.map.ObjectMapper;
import de.rcenvironment.components.doe.common.DOEAlgorithms;
import de.rcenvironment.components.doe.common.DOEComponentHistoryDataItem;
import de.rcenvironment.components.doe.common.DOEConstants;
import de.rcenvironment.components.doe.common.DOEUtils;
import de.rcenvironment.core.component.api.ComponentConstants;
import de.rcenvironment.core.component.api.ComponentException;
import de.rcenvironment.core.component.api.LoopComponentConstants;
import de.rcenvironment.core.component.datamanagement.api.ComponentDataManagementService;
import de.rcenvironment.core.component.execution.api.ThreadHandler;
import de.rcenvironment.core.component.model.spi.AbstractNestedLoopComponent;
import de.rcenvironment.core.datamodel.api.DataType;
import de.rcenvironment.core.datamodel.api.TypedDatumService;
import de.rcenvironment.core.datamodel.types.api.FileReferenceTD;
import de.rcenvironment.core.datamodel.types.api.FloatTD;
import de.rcenvironment.core.utils.common.JsonUtils;
import de.rcenvironment.core.utils.common.StringUtils;
import de.rcenvironment.core.utils.common.TempFileServiceAccess;
/**
* Component for doing a design of experiments.
*
* @author Sascha Zur
* @author Doreen Seider (logging)
* @author Jascha Riedel (#14117)
*/
public class DOEComponent extends AbstractNestedLoopComponent {
private static final String PLACEHOLDER_STRING = "%s: %s";
private static final String WROTE_VALUE_TO_OUTPUT_TEXT = "Wrote to output '%s': %s";
private static final Log LOGGER = LogFactory.getLog(DOEComponent.class);
private Double[][] valuesTable;
private final Map<Integer, Map<String, Double>> resultData = new HashMap<>();
private int runNumber = 0;
private int endSample = 0;
private DOEComponentHistoryDataItem historyDataItem;
private FileReferenceTD tableFileReference;
private Double[][] codedValues;
private List<String> outputs;
private boolean isDone;
private File tableFile;
private volatile boolean canceled = false;
@Override
public void startNestedComponentSpecific() throws ComponentException {
outputs = new LinkedList<>(componentContext.getOutputs());
removeOutputsNotConsidered();
Collections.sort(outputs);
int runNumberCount = Integer.parseInt(componentContext.getConfigurationValue(DOEConstants.KEY_RUN_NUMBER));
int seedNumber = 0;
if (componentContext.getConfigurationValue(DOEConstants.KEY_SEED_NUMBER) != null) {
seedNumber = Integer.parseInt(componentContext.getConfigurationValue(DOEConstants.KEY_SEED_NUMBER));
}
valuesTable = new Double[0][0];
if (!(componentContext.getConfigurationValue(DOEConstants.KEY_METHOD).equals(DOEConstants.DOE_ALGORITHM_CUSTOM_TABLE)
|| componentContext.getConfigurationValue(DOEConstants.KEY_METHOD).equals(DOEConstants.DOE_ALGORITHM_MONTE_CARLO))
&& outputs.size() < 2) {
throw new ComponentException("Number of outputs for chosen method too few - must be >=2, but is " + outputs.size());
}
switch (componentContext.getConfigurationValue(DOEConstants.KEY_METHOD)) {
case DOEConstants.DOE_ALGORITHM_FULLFACT:
if (runNumberCount >= 2) {
valuesTable = DOEAlgorithms.populateTableFullFactorial(outputs.size(), runNumberCount);
if (valuesTable.length == 0) {
throw new ComponentException("The chosen configuration produced too many samples");
}
} else {
throw new ComponentException("Level number for full factorial design too low - must be >=2, but is " + runNumberCount);
}
break;
case DOEConstants.DOE_ALGORITHM_LHC:
valuesTable = DOEAlgorithms.populateTableLatinHypercube(outputs.size(), runNumberCount, seedNumber);
break;
case DOEConstants.DOE_ALGORITHM_MONTE_CARLO:
valuesTable = DOEAlgorithms.populateTableMonteCarlo(outputs.size(), runNumberCount, seedNumber);
break;
case DOEConstants.DOE_ALGORITHM_CUSTOM_TABLE:
ObjectMapper mapper = JsonUtils.getDefaultObjectMapper();
try {
if (componentContext.getConfigurationValue(DOEConstants.KEY_TABLE) != null
&& !componentContext.getConfigurationValue(DOEConstants.KEY_TABLE).isEmpty()) {
this.valuesTable = mapper.readValue(componentContext.getConfigurationValue(DOEConstants.KEY_TABLE), Double[][].class);
if (valuesTable == null) {
throw new ComponentException("No table given");
}
} else {
throw new ComponentException("No table given");
}
if (componentContext.getConfigurationValue(DOEConstants.KEY_START_SAMPLE) != null
&& !componentContext.getConfigurationValue(DOEConstants.KEY_START_SAMPLE).isEmpty()) {
this.runNumber = Integer.parseInt(componentContext.getConfigurationValue(DOEConstants.KEY_START_SAMPLE));
}
if (componentContext.getConfigurationValue(DOEConstants.KEY_END_SAMPLE) != null
&& !componentContext.getConfigurationValue(DOEConstants.KEY_END_SAMPLE).isEmpty()) {
this.endSample = Integer.parseInt(componentContext.getConfigurationValue(DOEConstants.KEY_END_SAMPLE));
}
if (this.runNumber < 0) {
componentLog.componentInfo("Start sample value < 0 -> set it to 0");
this.runNumber = 0;
}
if (this.runNumber >= valuesTable.length) {
throw new ComponentException(StringUtils.format("Start sample value (%s) is greater than the number of samples (%s)",
this.runNumber,
valuesTable.length));
}
if (this.runNumber > this.endSample) {
throw new ComponentException(StringUtils.format("Start sample value (%s) is greater than end sample value (%s)",
this.runNumber,
this.endSample));
}
if (valuesTable.length > 0 && valuesTable[0].length < outputs.size()) {
throw new ComponentException(StringUtils.format(
"Number of values per sample (%s) is lower than the number of outputs (%s)",
valuesTable[0].length,
outputs.size()));
}
for (int i = 0; i < endSample && i < valuesTable.length; i++) {
for (int j = 0; j < valuesTable[i].length; j++) {
if (valuesTable[i][j] == null) {
throw new ComponentException("Values in table are uncomplete");
}
}
}
} catch (IOException e) {
throw new ComponentException("Failed to read given table", e);
}
break;
default:
break;
}
try {
int i = 0;
codedValues = new Double[valuesTable.length][valuesTable[0].length];
for (String output : outputs) {
Double low = Double.valueOf(componentContext.getOutputMetaDataValue(output, DOEConstants.META_KEY_LOWER));
Double up = Double.valueOf(componentContext.getOutputMetaDataValue(output, DOEConstants.META_KEY_UPPER));
for (int run = 0; run < valuesTable.length && run <= endSample; run++) {
if (valuesTable[run][i] != null) {
codedValues[run][i] = DOEAlgorithms.convertValue(low, up, valuesTable[run][i]);
}
}
i++;
}
if (outputs.size() > 0) {
tableFile = TempFileServiceAccess.getInstance().createTempFileFromPattern("DOETable*.csv");
DOEUtils.writeTableToCSVFile(codedValues, tableFile.getAbsolutePath(), outputs);
}
} catch (IOException e) {
String errorMessage = "Failed to write DOE table file";
componentLog.componentError(StringUtils.format(PLACEHOLDER_STRING, errorMessage, e.getMessage()));
LOGGER.error(errorMessage, e);
}
if (treatStartAsComponentRun()) {
processInputsNestedComponentSpecific();
}
}
private void removeOutputsNotConsidered() {
outputs.remove(LoopComponentConstants.ENDPOINT_NAME_LOOP_DONE);
outputs.remove(DOEConstants.OUTPUT_NAME_NUMBER_OF_SAMPLES);
Iterator<String> outputsIterator = outputs.iterator();
while (outputsIterator.hasNext()) {
String outputName = outputsIterator.next();
if (componentContext.isDynamicOutput(outputName)
&& componentContext.getDynamicOutputIdentifier(outputName).equals(LoopComponentConstants.ENDPOINT_ID_TO_FORWARD)) {
outputsIterator.remove();
}
}
}
@Override
public boolean treatStartAsComponentRun() {
return !hasForwardingStartInputs();
}
@Override
public void processInputsNestedComponentSpecific() throws ComponentException {
initializeNewHistoryDataItem();
if (historyDataItem != null && tableFileReference != null) {
historyDataItem.setTableFileReference(tableFileReference.getFileReference());
} else if (historyDataItem != null) {
createTableFileReference();
}
processInput();
if (runNumber == 0) {
componentContext.writeOutput(DOEConstants.OUTPUT_NAME_NUMBER_OF_SAMPLES, typedDatumFactory.createInteger(valuesTable.length));
}
writeNewOutput();
writeResultFile();
}
private void createTableFileReference() {
if (historyDataItem != null) {
try {
tableFileReference =
componentContext.getService(ComponentDataManagementService.class).createFileReferenceTDFromLocalFile(componentContext,
tableFile, "DOETable.csv");
} catch (IOException e) {
String errorMessage = "Failed to create DOE table file";
componentLog.componentError(StringUtils.format(PLACEHOLDER_STRING, errorMessage, e.getMessage()));
LOGGER.error(errorMessage, e);
} finally {
if (tableFile != null && tableFile.exists()) {
try {
TempFileServiceAccess.getInstance().disposeManagedTempDirOrFile(tableFile);
} catch (IOException e) {
LOGGER.error("Failed to dispose temporary file: " + tableFile.getAbsolutePath(), e);
}
}
}
historyDataItem.setTableFileReference(tableFileReference.getFileReference());
}
}
@Override
public void completeStartOrProcessInputsAfterFailure() throws ComponentException {
writeResultFile();
writeFinalHistoryDataItem();
}
@Override
public void onStartInterrupted(ThreadHandler executingThreadHandler) {
canceled = true;
}
@Override
public void tearDown(FinalComponentState state) {
super.tearDown(state);
if (tableFile != null && tableFile.exists()) {
try {
TempFileServiceAccess.getInstance().disposeManagedTempDirOrFile(tableFile);
} catch (IOException e) {
LOGGER.error("Could not dispose temp file: ", e);
}
}
}
private void writeResultFile() {
if (!componentContext.getInputsWithDatum().isEmpty() && historyDataItem != null) {
File resultFile = null;
try {
resultFile = TempFileServiceAccess.getInstance().createTempFileFromPattern("DOEResult*.csv");
DOEUtils.writeResultToCSVFile(codedValues, resultData, resultFile.getAbsolutePath(), runNumber, outputs);
FileReferenceTD resultFileReference =
componentContext.getService(ComponentDataManagementService.class).createFileReferenceTDFromLocalFile(componentContext,
resultFile, "Result.csv");
historyDataItem.setResultFileReference(resultFileReference.getFileReference());
} catch (IOException e) {
String errorMessage = "Failed to store history data";
componentLog.componentError(StringUtils.format(PLACEHOLDER_STRING, errorMessage, e.getMessage()));
LOGGER.error(errorMessage, e);
} finally {
if (resultFile != null && resultFile.exists()) {
try {
TempFileServiceAccess.getInstance().disposeManagedTempDirOrFile(resultFile);
} catch (IOException e) {
LOGGER.error("Failed to dispose temporary file: " + resultFile.getAbsolutePath(), e);
}
}
}
}
}
private void writeNewOutput() {
if (valuesTable != null) {
if (componentContext.getInputs().isEmpty() && !hasForwardingStartInputs()) {
writeAllOutputs();
} else if (runNumber < valuesTable.length) {
writeNextOutput();
} else {
setLoopDone();
}
}
}
private void setLoopDone() {
setLoopDone(true);
}
private void setLoopDone(boolean done) {
isDone = done;
}
private void writeNextOutput() {
if (componentContext.getConfigurationValue(DOEConstants.KEY_METHOD).equals(DOEConstants.DOE_ALGORITHM_CUSTOM_TABLE)
&& runNumber > endSample) {
setLoopDone();
return;
}
int i = 0;
for (String output : outputs) {
Double low = Double.valueOf(componentContext.getOutputMetaDataValue(output, DOEConstants.META_KEY_LOWER));
Double up = Double.valueOf(componentContext.getOutputMetaDataValue(output, DOEConstants.META_KEY_UPPER));
double value = valuesTable[runNumber][i++];
if (!componentContext.getConfigurationValue(DOEConstants.KEY_METHOD)
.equals(DOEConstants.DOE_ALGORITHM_CUSTOM_TABLE)) {
value = DOEAlgorithms.convertValue(low, up, value);
}
writeOutput(output,
componentContext.getService(TypedDatumService.class).getFactory().createFloat(value));
componentLog.componentInfo(StringUtils.format(WROTE_VALUE_TO_OUTPUT_TEXT, output, value));
}
runNumber++;
setLoopDone(false);
}
private void writeAllOutputs() {
while (runNumber < valuesTable.length) {
if (componentContext.getConfigurationValue(DOEConstants.KEY_METHOD).equals(DOEConstants.DOE_ALGORITHM_CUSTOM_TABLE)
&& runNumber > endSample) {
break;
}
int i = 0;
for (String output : outputs) {
Double low = Double.valueOf(componentContext.getOutputMetaDataValue(output, DOEConstants.META_KEY_LOWER));
Double up = Double.valueOf(componentContext.getOutputMetaDataValue(output, DOEConstants.META_KEY_UPPER));
double value = valuesTable[runNumber][i++];
if (!componentContext.getConfigurationValue(DOEConstants.KEY_METHOD)
.equals(DOEConstants.DOE_ALGORITHM_CUSTOM_TABLE)) {
value = DOEAlgorithms.convertValue(low, up, value);
}
writeOutput(output,
componentContext.getService(TypedDatumService.class).getFactory().createFloat(value));
componentLog.componentInfo(StringUtils.format(WROTE_VALUE_TO_OUTPUT_TEXT, output, value));
}
runNumber++;
if (canceled) {
break;
}
}
setLoopDone();
}
private void processInput() throws ComponentException {
if (!componentContext.getInputsWithDatum().isEmpty()) {
Map<String, Double> runInput = new HashMap<>();
for (String inputName : componentContext.getInputsWithDatum()) {
if (componentContext.getDynamicInputIdentifier(inputName).equals(DOEConstants.INPUT_ID_NAME)) {
if (componentContext.readInput(inputName).getDataType() != DataType.NotAValue) {
runInput.put(inputName, ((FloatTD) componentContext.readInput(inputName)).getFloatValue());
} else {
runInput.put(inputName, Double.NaN);
}
}
}
resultData.put(runNumber - 1, runInput);
}
}
private void initializeNewHistoryDataItem() {
if (Boolean.valueOf(componentContext.getConfigurationValue(ComponentConstants.CONFIG_KEY_STORE_DATA_ITEM))) {
historyDataItem = new DOEComponentHistoryDataItem();
}
}
private void writeFinalHistoryDataItem() {
if (historyDataItem != null && outputs.size() > 0
&& Boolean.valueOf(componentContext.getConfigurationValue(ComponentConstants.CONFIG_KEY_STORE_DATA_ITEM))) {
componentContext.writeFinalHistoryDataItem(historyDataItem);
}
}
@Override
protected boolean isDoneNestedComponentSpecific() {
return isDone;
}
@Override
protected void resetNestedComponentSpecific() {
runNumber = 0;
isDone = false;
}
@Override
protected void finishLoopNestedComponentSpecific() {
}
@Override
protected void sendFinalValues() throws ComponentException {
writeFinalHistoryDataItem();
}
@Override
protected void sendValuesNestedComponentSpecific() {
writeFinalHistoryDataItem();
}
}