|
@@ -0,0 +1,246 @@
|
|
|
+package ua.ansymo.hintco
|
|
|
+
|
|
|
+import java.util.HashMap
|
|
|
+import java.util.List
|
|
|
+import java.util.Map
|
|
|
+import org.eclipse.core.runtime.Assert
|
|
|
+
|
|
|
+import static ua.ansymo.hintco.CosimRunUtils.*
|
|
|
+
|
|
|
+class WaveformCosimRunner extends CosimRunner {
|
|
|
+ /*
|
|
|
+ * Runs the waveform relaxation version of the co-simulation.
|
|
|
+ * Does not require FMUs to be able to rollback
|
|
|
+ */
|
|
|
+
|
|
|
+ new(IOutputProcessor o, IFmuLoader l) {
|
|
|
+ super(o, l)
|
|
|
+ }
|
|
|
+
|
|
|
+ override run(RootCandidateScenario rootScenario, Map<Scenario, List<PrecendenceNode>> scenarioNodesMap, String variantID) {
|
|
|
+ Assert.isTrue(scenarioNodesMap.values.forall[sortedNodes | sortedNodes.forall[n1 | rootScenario.eAllContents.exists[n2 | n1===n2]]])
|
|
|
+
|
|
|
+ val nodes = scenarioNodesMap.get(rootScenario)
|
|
|
+
|
|
|
+ val units = nodes.filter(UnitInstance).toList
|
|
|
+ // Takes into account hierarchical unit ports.
|
|
|
+ val childOutputPorts = nodes.filter(OutputPortInstance).filter[!it.isInput].toList
|
|
|
+ val childInputPorts = nodes.filter(InputPortInstance).filter[it.isInput].toSet
|
|
|
+
|
|
|
+ // Instantiate each unit, and record the instance reference.
|
|
|
+ instantiateUnits(units, fmuInstanceMap, fmuLoader, scenarioNodesMap)
|
|
|
+
|
|
|
+ var HashMap<OutputPortInstance, Double>[] cosimSignals = newArrayOfSize(Math.toIntExact(Math.round(rootScenario.stopTime/rootScenario.stepSize)+2))
|
|
|
+
|
|
|
+ // Run a single cosim to initialize all output signals.
|
|
|
+ // This will ensure that we get a decent initial output.
|
|
|
+ {
|
|
|
+ logger.debug("Running initial co-simulation to populate signals")
|
|
|
+
|
|
|
+ val outVals = newHashMap() // Will store output values
|
|
|
+
|
|
|
+ // setup experiment
|
|
|
+ setupExperiment(units, fmuInstanceMap)
|
|
|
+
|
|
|
+ // Go into init mode
|
|
|
+ intoInitMode(units, fmuInstanceMap)
|
|
|
+
|
|
|
+ val MAX_IT = 10 // TODO: refactor. Should be a configuration parameter.
|
|
|
+
|
|
|
+ fixPointOutputs(childOutputPorts, fmuInstanceMap, MAX_IT)
|
|
|
+
|
|
|
+ // compute first line
|
|
|
+ outVals.clear()
|
|
|
+ getOutputSnapshot(units, fmuInstanceMap, outVals)
|
|
|
+ cosimSignals.set(0,outVals.clone() as HashMap<OutputPortInstance, Double>)
|
|
|
+ logger.debug("Output line recorded for time {} and step {}.", 0.0, 0.0)
|
|
|
+
|
|
|
+ exitInitMode(units, fmuInstanceMap)
|
|
|
+
|
|
|
+ // Run the co-sim scenario
|
|
|
+ var time = 0.0d
|
|
|
+ val stepSize = rootScenario.stepSize
|
|
|
+ val stopTime = rootScenario.stopTime
|
|
|
+ var i = 0
|
|
|
+ while (time < stopTime || MathUtils.isApproximatelyEqual(time, stopTime, stepSize*1e-3)){
|
|
|
+ executeStep(rootScenario, time, stepSize, nodes, units, childInputPorts, fmuInstanceMap)
|
|
|
+
|
|
|
+ // Advance time
|
|
|
+ logger.debug("Time advance: {} -> {}", time, time + stepSize)
|
|
|
+
|
|
|
+ time = time + stepSize
|
|
|
+ i = i + 1
|
|
|
+
|
|
|
+ // Take output snaptshot. Note that this is different than saving the outputs obtained during the cosim step.
|
|
|
+ outVals.clear()
|
|
|
+ getOutputSnapshot(units, fmuInstanceMap, outVals)
|
|
|
+ cosimSignals.set(i,outVals.clone() as HashMap<OutputPortInstance, Double>)
|
|
|
+ logger.debug("Output line recorded for time {} and step {}.", time, i)
|
|
|
+ }
|
|
|
+ // Cleanup
|
|
|
+ resetUnits(units, fmuInstanceMap)
|
|
|
+
|
|
|
+ logger.debug("Initial cosim terminated.")
|
|
|
+ }
|
|
|
+
|
|
|
+ // Now the initial output signals are in cosimSignals. So we start the iteration
|
|
|
+
|
|
|
+ // Maximum number of cosims to be run
|
|
|
+ val MAX_COSIMS = 4
|
|
|
+ var cosimCounter = 0
|
|
|
+ var waveformIterationConverged = false
|
|
|
+ var newCosimSignals = newArrayOfSize(cosimSignals.size)
|
|
|
+ val MAX_RTOL_CONVERGENCE = 1e-4
|
|
|
+ val output_prefix = variantID+"wvform_"
|
|
|
+
|
|
|
+ while (!waveformIterationConverged && cosimCounter < MAX_COSIMS){
|
|
|
+ logger.debug("Cosim number {} started", cosimCounter)
|
|
|
+
|
|
|
+ var cosimStep = 0
|
|
|
+
|
|
|
+ waveformIterationConverged=true // Will be flipped to false if two signals are too distinct.
|
|
|
+
|
|
|
+ var prevOutVals = cosimSignals.get(cosimStep)
|
|
|
+
|
|
|
+ // setup experiment
|
|
|
+ setupExperiment(units, fmuInstanceMap)
|
|
|
+
|
|
|
+ // Go into init mode
|
|
|
+ intoInitMode(units, fmuInstanceMap)
|
|
|
+
|
|
|
+ outProcessor.initialize(rootScenario, output_prefix+cosimCounter)
|
|
|
+
|
|
|
+ // For each unit, set the inputs previously recorded in cosimSignals and record the new outputs in newCosimSignals
|
|
|
+ for (out : childOutputPorts){
|
|
|
+ val outV = prevOutVals.get(out)
|
|
|
+ logger.debug("GetReal {}.{} at initialization yields {}.", out.unit.identifier, out.identifier, outV)
|
|
|
+
|
|
|
+ // Propagate outV to inputs.
|
|
|
+ for (trgPort : out.valueTo){
|
|
|
+ fmuInstanceMap.get(trgPort.unit).setReal(trgPort.identifier, outV)
|
|
|
+ logger.debug("SetReal {}.{} to {}.", trgPort.unit.identifier, trgPort.identifier, outV)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // compute first line. The initial signals have already converged, so the first line is prevOutVals
|
|
|
+ newCosimSignals.set(0,prevOutVals.clone() as HashMap<OutputPortInstance, Double>)
|
|
|
+
|
|
|
+ outProcessor.setOutputs(0, 0.0, prevOutVals)
|
|
|
+ logger.debug("Output line recorded for cosim {}, time {} and step {}.", cosimCounter, 0.0, 0.0)
|
|
|
+
|
|
|
+ exitInitMode(units, fmuInstanceMap)
|
|
|
+
|
|
|
+ // Run the co-sim scenario
|
|
|
+ val outVals = newHashMap() // Will store output values of each cosim step.
|
|
|
+ var time = 0.0d
|
|
|
+ val stepSize = rootScenario.stepSize
|
|
|
+ val stopTime = rootScenario.stopTime
|
|
|
+ while (time < stopTime || MathUtils.isApproximatelyEqual(time, stopTime, stepSize*1e-3)){
|
|
|
+ // Run single cosim step.
|
|
|
+ {
|
|
|
+ logger.debug("Co-simulation step of {} start at time {} -> {}.", rootScenario.identifier, time, time + stepSize)
|
|
|
+
|
|
|
+ cosimStep=cosimStep+1
|
|
|
+
|
|
|
+ prevOutVals = cosimSignals.get(cosimStep)
|
|
|
+
|
|
|
+ // Let units know that a cosim step is starting.
|
|
|
+ for (u : units){
|
|
|
+ fmuInstanceMap.get(u).startCosimStep(time, stepSize)
|
|
|
+ }
|
|
|
+
|
|
|
+ // For each unit, set the inputs previously recorded in cosimSignals
|
|
|
+ for (out : childOutputPorts){
|
|
|
+ val prevOutV = prevOutVals.get(out)
|
|
|
+
|
|
|
+ // Propagate prevOutV to inputs.
|
|
|
+ for (trgPort : out.valueTo){
|
|
|
+ fmuInstanceMap.get(trgPort.unit).setReal(trgPort.identifier, prevOutV)
|
|
|
+ logger.debug("SetReal {}.{} to {}.", trgPort.unit.identifier, trgPort.identifier, prevOutV)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Step units
|
|
|
+ for (u : units){
|
|
|
+ fmuInstanceMap.get(u).doStep(time,stepSize)
|
|
|
+ logger.debug("DoStep {} at time {}.", u.identifier, time)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Let units know that a cosim step has finished.
|
|
|
+ for (u : units){
|
|
|
+ fmuInstanceMap.get(u).endCosimStep(time, stepSize)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Record new outputs, and check if they diverge
|
|
|
+ // Take output snaptshot. Note that this is different than saving the outputs obtained during the cosim step.
|
|
|
+ outVals.clear()
|
|
|
+ getOutputSnapshot(units, fmuInstanceMap, outVals)
|
|
|
+ outProcessor.setOutputs(cosimStep, time, outVals)
|
|
|
+ logger.debug("Output line recorded for time {} and step {}.", time, cosimStep)
|
|
|
+
|
|
|
+ for (out : childOutputPorts){
|
|
|
+ val prevOutV = prevOutVals.get(out)
|
|
|
+ val outV = outVals.get(out)
|
|
|
+ // Check if outV has changed. This could be done at the end of the cosim, but here it is faster.
|
|
|
+ // If any new output is sufficiently different than the previous output, we know that we'll have to repeat the cosim
|
|
|
+ if (!MathUtils.isApproximatelyEqual(outV, prevOutV, MAX_RTOL_CONVERGENCE)){
|
|
|
+ logger.debug("Values {} and {} too different on port {}.{}. Cosim will be repeated.", outV, prevOutV, out.unit.identifier, out.identifier)
|
|
|
+ waveformIterationConverged = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ logger.debug("Co-simulation step of {} end at time {} -> {}.", rootScenario.identifier, time, time + stepSize)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Advance time
|
|
|
+ logger.debug("Time advance: {} -> {}", time, time + stepSize)
|
|
|
+
|
|
|
+ time = time + stepSize
|
|
|
+
|
|
|
+ // Commit outputs
|
|
|
+ // Record all the outputs that where not produced during the co-simulation step.
|
|
|
+ // Note that the values in outVals might not be all in time time because getOutputs can be invoked before doStep of the same fmu.
|
|
|
+
|
|
|
+ // Refresh outputs
|
|
|
+ newCosimSignals.set(cosimStep,outVals.clone() as HashMap<OutputPortInstance, Double>)
|
|
|
+ logger.debug("Output line recorded for time {} and step {}.", time, cosimStep)
|
|
|
+ }
|
|
|
+ // Cleanup
|
|
|
+ resetUnits(units, fmuInstanceMap)
|
|
|
+
|
|
|
+ logger.debug("Cosim number {} terminated.", cosimCounter)
|
|
|
+
|
|
|
+ outProcessor.terminate()
|
|
|
+
|
|
|
+ cosimCounter=cosimCounter+1
|
|
|
+
|
|
|
+ // Swap previous and current (double buffering), to avoid more memory allocation.
|
|
|
+ val aux = cosimSignals
|
|
|
+ cosimSignals = newCosimSignals
|
|
|
+ newCosimSignals = aux
|
|
|
+ }
|
|
|
+
|
|
|
+ // Here, the co-simulation has converged, and the cosimSignals contains the converged results.
|
|
|
+ // So we pass these to the output processor.
|
|
|
+ logger.debug("Waveform iteration terminated after {} iterations.", cosimCounter)
|
|
|
+ if (cosimCounter >= MAX_COSIMS){
|
|
|
+ logger.warn("Waveform iteration exceeded maximum number of co-simulations.")
|
|
|
+ }
|
|
|
+
|
|
|
+ {// Print final outputs
|
|
|
+ outProcessor.initialize(rootScenario, variantID)
|
|
|
+ for (cosimStep : 0..<cosimSignals.size) {
|
|
|
+ val time = cosimStep*rootScenario.stepSize
|
|
|
+ outProcessor.setOutputs(cosimStep, time, cosimSignals.get(cosimStep))
|
|
|
+ }
|
|
|
+ outProcessor.terminate()
|
|
|
+ }
|
|
|
+
|
|
|
+ logger.debug("Outputs produced.")
|
|
|
+
|
|
|
+ freeUnits(units, fmuInstanceMap)
|
|
|
+
|
|
|
+ logger.debug("Cleanup complete.")
|
|
|
+ }
|
|
|
+
|
|
|
+}
|