/*
* JaamSim Discrete Event Simulation
* Copyright (C) 2015 Ausenco Engineering Canada Inc.
* Copyright (C) 2015 JaamSim Software Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jaamsim.BasicObjects;
import java.util.ArrayList;
import com.jaamsim.Graphics.DisplayEntity;
import com.jaamsim.Samples.SampleListInput;
import com.jaamsim.Samples.SampleProvider;
import com.jaamsim.StringProviders.StringProvListInput;
import com.jaamsim.StringProviders.StringProvider;
import com.jaamsim.basicsim.EntityTarget;
import com.jaamsim.basicsim.FileEntity;
import com.jaamsim.basicsim.Simulation;
import com.jaamsim.datatypes.IntegerVector;
import com.jaamsim.events.Conditional;
import com.jaamsim.events.EventManager;
import com.jaamsim.events.ProcessTarget;
import com.jaamsim.input.BooleanInput;
import com.jaamsim.input.EntityListInput;
import com.jaamsim.input.Input;
import com.jaamsim.input.InputAgent;
import com.jaamsim.input.InputErrorException;
import com.jaamsim.input.IntegerListInput;
import com.jaamsim.input.Keyword;
import com.jaamsim.input.UnitTypeListInput;
import com.jaamsim.input.ValueInput;
import com.jaamsim.states.StateEntity;
import com.jaamsim.states.StateEntityListener;
import com.jaamsim.states.StateRecord;
import com.jaamsim.units.TimeUnit;
import com.jaamsim.units.Unit;
import com.jaamsim.units.UserSpecifiedUnit;
public class ExpressionLogger extends DisplayEntity implements StateEntityListener {
private FileEntity file;
@Keyword(description = "A fixed interval at which entries will be written to the log file. "
+ "This input is optional if state tracing or value tracing is specified.",
exampleList = { "24.0 h" })
private final ValueInput interval;
@Keyword(description = "The unit types for the quantities being logged. "
+ "Use DimensionlessUnit for text entries.",
exampleList = {"DistanceUnit SpeedUnit"})
private final UnitTypeListInput unitTypeListInput;
@Keyword(description = "One or more sources of data to be logged. "
+ "Each source is specified by an Expression. Also acceptable are: "
+ "a constant value, a Probability Distribution, TimeSeries, or a "
+ "Calculation Object.",
exampleList = {"{ [Entity1].Output1 } { [Entity2].Output2 }"})
protected final StringProvListInput dataSource;
@Keyword(description = "If TRUE, entries are logged during the initialization period.",
exampleList = { "FALSE" })
private final BooleanInput includeInitialization;
@Keyword(description = "The time for the first log entry.",
exampleList = { "24.0 h" })
private final ValueInput startTime;
@Keyword(description = "The latest time at which to make an entry in the log.",
exampleList = { "8760.0 h" })
private final ValueInput endTime;
@Keyword(description = "A list of entities whose states will be traced. "
+ "An entry in the log file is made every time one of the entities changes state. "
+ "Each entity's state is written automatically to the log file - it is not necessary "
+ "to add an expression to the DataSource keyword's input.",
exampleList = { "Server1 ExpressionThreshold1" })
private final EntityListInput<StateEntity> stateTraceList;
@Keyword(description = "The unit types for the values being traced.",
exampleList = {"DistanceUnit SpeedUnit"})
private final UnitTypeListInput valueUnitTypeList;
@Keyword(description = "One or more sources of data whose values will be traced. An entry in "
+ "the log file is made every time one of the data sources changes value. "
+ "Each data source's value is written automatically to the log file - it is not "
+ "necessary to add an expression to the DataSource keyword's input.\n\n"
+ "Each source is specified by an Expression. Also acceptable are: "
+ "a constant value, a Probability Distribution, TimeSeries, or a Calculation Object.",
exampleList = {"{ [Entity1].Output1 } { [Entity2].Output2 }"})
private final SampleListInput valueTraceList;
@Keyword(description = "The number of decimal places to show for each value in valueTraceList."
+ " If only one number is given, then that number of decimal places is used for all values.",
exampleList = "1 1")
private final IntegerListInput valuePrecisionList;
private final ArrayList<Double> lastValueList = new ArrayList<>();
{
interval = new ValueInput("Interval", "Key Inputs", null);
interval.setUnitType(TimeUnit.class);
interval.setValidRange(1.0e-10, Double.POSITIVE_INFINITY);
this.addInput(interval);
ArrayList<Class<? extends Unit>> defList = new ArrayList<>();
unitTypeListInput = new UnitTypeListInput("UnitTypeList", "Key Inputs", defList);
unitTypeListInput.setDefaultText("None");
this.addInput(unitTypeListInput);
dataSource = new StringProvListInput("DataSource", "Key Inputs",
new ArrayList<StringProvider>());
dataSource.setUnitType(UserSpecifiedUnit.class);
dataSource.setEntity(this);
dataSource.setDefaultText("None");
this.addInput(dataSource);
includeInitialization = new BooleanInput("IncludeInitialization", "Key Inputs", true);
this.addInput(includeInitialization);
startTime = new ValueInput("StartTime", "Key Inputs", 0.0d);
startTime.setUnitType(TimeUnit.class);
startTime.setValidRange(0.0d, Double.POSITIVE_INFINITY);
this.addInput(startTime);
endTime = new ValueInput("EndTime", "Key Inputs", Double.POSITIVE_INFINITY);
endTime.setUnitType(TimeUnit.class);
endTime.setValidRange(0.0d, Double.POSITIVE_INFINITY);
this.addInput(endTime);
stateTraceList = new EntityListInput<>(StateEntity.class, "StateTraceList", "Tracing",
new ArrayList<StateEntity>());
this.addInput(stateTraceList);
ArrayList<Class<? extends Unit>> valDefList = new ArrayList<>();
valueUnitTypeList = new UnitTypeListInput("ValueUnitTypeList", "Tracing",
valDefList);
valueUnitTypeList.setDefaultText("None");
this.addInput(valueUnitTypeList);
valueTraceList = new SampleListInput("ValueTraceList", "Tracing",
new ArrayList<SampleProvider>());
valueTraceList.setUnitType(UserSpecifiedUnit.class);
valueTraceList.setEntity(this);
valueTraceList.setDefaultText("None");
this.addInput(valueTraceList);
valuePrecisionList = new IntegerListInput("ValuePrecisionList", "Tracing", new IntegerVector());
this.addInput(valuePrecisionList);
}
public ExpressionLogger() {}
@Override
public void updateForInput(Input<?> in) {
super.updateForInput(in);
if (in == valueUnitTypeList) {
valueTraceList.setUnitTypeList(valueUnitTypeList.getUnitTypeList());
return;
}
if (in == valueTraceList) {
lastValueList.clear();
for (int i=0; i<valueTraceList.getListSize(); i++) {
lastValueList.add(Double.NaN);
}
return;
}
if (in == unitTypeListInput) {
dataSource.setUnitTypeList(unitTypeListInput.getUnitTypeList());
return;
}
}
@Override
public void validate() {
super.validate();
if (valuePrecisionList.getValue().size() > 1) {
if (valuePrecisionList.getValue().size() != valueTraceList.getValue().size()) {
throw new InputErrorException( "There must be the same number of entries in ValueTraceList and ValuePrecisionList" );
}
}
}
@Override
public void earlyInit() {
super.earlyInit();
// Close the file if it is already open
if (file != null && Simulation.isFirstRun()) {
file.close();
file = null;
}
// Create the report file
if (file == null) {
StringBuilder tmp = new StringBuilder(InputAgent.getReportFileName(InputAgent.getRunName()));
tmp.append("-").append(this.getName());
tmp.append(".log");
file = new FileEntity(tmp.toString());
}
}
@Override
public void startUp() {
super.startUp();
// Print run number header if multiple runs are to be performed
if (Simulation.isMultipleRuns()) {
if (!Simulation.isFirstRun())
file.format("%n%n");
file.format("%s%n", Simulation.getRunHeader());
}
// WRITE THE HEADER LINE
// a) Simulation time
file.format("%n%s", "SimTime");
// b) Traced entities
for (StateEntity ent : stateTraceList.getValue()) {
file.format("\t%s", ent.getName());
}
// c) Traced values
ArrayList<String> valToks = new ArrayList<>();
valueTraceList.getValueTokens(valToks);
for (String str : valToks) {
if (str.equals("{") || str.equals("}"))
continue;
file.format("\t%s", str);
}
// d) Logged expressions
ArrayList<String> toks = new ArrayList<>();
dataSource.getValueTokens(toks);
for (String str : toks) {
if (str.equals("{") || str.equals("}"))
continue;
file.format("\t%s", str);
}
// WRITE THE UNITS LINE
// a) Simulation time units
String unit = Unit.getDisplayedUnit(TimeUnit.class);
file.format("%n%s", unit);
// b) Traced entities
for (int i=0; i<stateTraceList.getValue().size(); i++) {
file.format("\tState");
}
// c) Traced values
for (int i=0; i<valueTraceList.getListSize(); i++) {
unit = Unit.getDisplayedUnit(valueTraceList.getUnitType(i));
file.format("\t%s", unit);
}
// d) Logged expressions
for (int i=0; i<dataSource.getListSize(); i++) {
unit = Unit.getDisplayedUnit(dataSource.getUnitType(i));
file.format("\t%s", unit);
}
// Empty the output buffer
file.flush();
// Start tracing the expression values
if (valueTraceList.getListSize() > 0)
this.doValueTrace();
// Start log entries at fixed intervals
if (interval.getValue() != null)
this.scheduleProcess(startTime.getValue(), 5, endActionTarget);
}
private void startAction() {
// Schedule the next time an entry in the log file will be written
this.scheduleProcess(interval.getValue(), 5, endActionTarget);
}
final void endAction() {
// Stop the log if the end time has been reached
double simTime = getSimTime();
if (simTime > endTime.getValue())
return;
// Record the entry in the log
this.recordEntry(simTime);
// Get ready for the next entry
this.startAction();
}
protected final ProcessTarget endActionTarget = new EndActionTarget(this);
private static class EndActionTarget extends EntityTarget<ExpressionLogger> {
EndActionTarget(ExpressionLogger ent) {
super(ent, "endAction");
}
@Override
public void process() {
ent.endAction();
}
}
/**
* Writes an entry to the log file.
*/
private void recordEntry(double simTime) {
// Skip the log entry if the run is still initializing
if (!includeInitialization.getValue() && simTime < Simulation.getInitializationTime())
return;
// Skip the log entry if it is outside the time range
if (simTime < startTime.getValue() || simTime > endTime.getValue())
return;
// Write the time for the log entry
double factor = Unit.getDisplayedUnitFactor(TimeUnit.class);
file.format("%n%s", simTime/factor);
// Write the state values
for (StateEntity ent : stateTraceList.getValue()) {
file.format("\t%s", ent.getPresentState(simTime));
}
try {
// Write the traced expression values
for (int i=0; i<valueTraceList.getListSize(); i++) {
double val = valueTraceList.getValue().get(i).getNextSample(simTime);
factor = Unit.getDisplayedUnitFactor(valueTraceList.getUnitType(i));
if (valuePrecisionList.getValue().size() == 1) {
int precision = valuePrecisionList.getValue().get(0);
file.format("\t%."+precision+"f", val/factor );
}
else if (valuePrecisionList.getValue().size() > 1) {
int precision = valuePrecisionList.getValue().get(i);
file.format("\t%."+precision+"f", val/factor );
}
else {
file.format("\t%s", val/factor);
}
// Update the last recorded values for the traced expressions
lastValueList.set(i, val);
}
// Write the expression values
for (int i=0; i<dataSource.getListSize(); i++) {
StringProvider samp = dataSource.getValue().get(i);
factor = Unit.getDisplayedUnitFactor(dataSource.getUnitType(i));
file.format("\t%s", samp.getNextString(simTime, "%s", factor));
}
}
catch (Exception e) {
error(e.getMessage());
}
// Empty the output buffer
file.flush();
}
@Override
public boolean isWatching(StateEntity ent) {
return stateTraceList.getValue().contains(ent);
}
@Override
public void updateForStateChange(StateEntity ent, StateRecord prev, StateRecord next) {
this.recordEntry(getSimTime());
}
/**
* Returns true if any of the traced expressions have changed their values.
*/
final boolean valueChanged() {
boolean ret = false;
double simTime = getSimTime();
try {
for (int i=0; i<valueTraceList.getListSize(); i++) {
double val = valueTraceList.getValue().get(i).getNextSample(simTime);
if (val != lastValueList.get(i)) {
lastValueList.set(i, val);
ret = true;
}
}
}
catch (Exception e) {
error(e.getMessage());
}
return ret;
}
/**
* Writes a record to the log file whenever one of the traced expressions changes its value.
*/
void doValueTrace() {
// Stop tracing if the end time has been reached
double simTime = getSimTime();
if (simTime > endTime.getValue())
return;
// Record the entry in the log
this.recordEntry(simTime);
// Wait for the next value change
EventManager.scheduleUntil(doValueTrace, valueChanged, null);
}
static class ValueChangedConditional extends Conditional {
private final ExpressionLogger ent;
ValueChangedConditional(ExpressionLogger ent) {
this.ent = ent;
}
@Override
public boolean evaluate() {
return ent.valueChanged();
}
}
private final Conditional valueChanged = new ValueChangedConditional(this);
class DoValueTraceTarget extends ProcessTarget {
@Override
public String getDescription() {
return ExpressionLogger.this.getName() + ".doValueTrace";
}
@Override
public void process() {
doValueTrace();
}
}
private final ProcessTarget doValueTrace = new DoValueTraceTarget();
}