Bläddra i källkod

strong coupling implemented without rollback

Claudio Gomes 6 år sedan
förälder
incheckning
7c168eb378

+ 6 - 2
HintCOEngine/instances/case_study_double_loop_approx.xmi

@@ -1,8 +1,12 @@
 <?xml version="1.0" encoding="ASCII"?>
-<hintco:HintConfiguration xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:hintco="ua.ansymo.hintco">
+<hintco:HintConfiguration
+    xmi:version="2.0"
+    xmlns:xmi="http://www.omg.org/XMI"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xmlns:hintco="ua.ansymo.hintco">
   <candidates
       identifier="Original"
-      stopTime="0.1"
+      stopTime="0.02"
       stepSize="1.0E-5"
       outputStepSize="0.01">
     <cosimunits

+ 2 - 2
HintCOEngine/instances/case_study_double_loop_default.hintco

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="ASCII"?>
-<hintco:HintConfiguration xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:hintco="ua.ansymo.hintco">
-  <candidates identifier="Original" stopTime="0.1" stepSize="1.0E-5" outputStepSize="0.01">
+<hintco:HintConfiguration xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:hintco="ua.ansymo.hintco">
+  <candidates identifier="Original" stopTime="0.02" stepSize="1.0E-5" outputStepSize="0.01">
     <cosimunits xsi:type="hintco:CosimUnitInstance" identifier="Scenario" declaration="//@csuDeclarations.0">
       <ports xsi:type="hintco:OutputPortInstance" identifier="psuvolt" valueTo="//@candidates.0/@cosimunits.2/@ports.1"/>
       <ports xsi:type="hintco:OutputPortInstance" identifier="ref" valueTo="//@candidates.0/@cosimunits.1/@ports.2"/>

+ 3 - 3
HintCOEngine/instances/waveform_msd.hintco

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="ASCII"?>
 <hintco:HintConfiguration xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:hintco="ua.ansymo.hintco">
-  <candidates identifier="Original" stopTime="8.0" stepSize="0.01" outputStepSize="0.01">
+  <candidates identifier="Original" stopTime="0.05" stepSize="0.01" outputStepSize="0.01">
     <cosimunits xsi:type="hintco:CosimUnitInstance" identifier="msd1" declaration="//@csuDeclarations.0">
       <ports xsi:type="hintco:OutputPortInstance" identifier="x1" valueTo="//@candidates.0/@cosimunits.1/@ports.0"/>
       <ports xsi:type="hintco:OutputPortInstance" identifier="v1" valueTo="//@candidates.0/@cosimunits.1/@ports.1"/>
@@ -10,10 +10,10 @@
     </cosimunits>
     <cosimunits xsi:type="hintco:CosimUnitInstance" identifier="msd2" declaration="//@csuDeclarations.1">
       <ports xsi:type="hintco:InputPortInstance" identifier="x1" valueFrom="//@candidates.0/@cosimunits.0/@ports.0">
-        <adaptation xsi:type="hintco:WaveformInterpolationAdaptation"/>
+        <adaptation xsi:type="hintco:RollbackInterpolationAdaptation"/>
       </ports>
       <ports xsi:type="hintco:InputPortInstance" identifier="v1" valueFrom="//@candidates.0/@cosimunits.0/@ports.1">
-        <adaptation xsi:type="hintco:WaveformInterpolationAdaptation"/>
+        <adaptation xsi:type="hintco:RollbackInterpolationAdaptation"/>
       </ports>
       <ports xsi:type="hintco:OutputPortInstance" identifier="fk" valueTo="//@candidates.0/@cosimunits.0/@ports.2"/>
       <ports xsi:type="hintco:OutputPortInstance" identifier="x2"/>

+ 33 - 0
HintCOEngine/instances/waveform_msd_normal.hintco

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="ASCII"?>
+<hintco:HintConfiguration xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:hintco="ua.ansymo.hintco">
+  <candidates identifier="Original" stopTime="0.05" stepSize="0.01" outputStepSize="0.01">
+    <cosimunits xsi:type="hintco:CosimUnitInstance" identifier="msd1" declaration="//@csuDeclarations.0">
+      <ports xsi:type="hintco:OutputPortInstance" identifier="x1" valueTo="//@candidates.0/@cosimunits.1/@ports.0"/>
+      <ports xsi:type="hintco:OutputPortInstance" identifier="v1" valueTo="//@candidates.0/@cosimunits.1/@ports.1"/>
+      <ports xsi:type="hintco:InputPortInstance" identifier="fk" valueFrom="//@candidates.0/@cosimunits.1/@ports.2">
+        <adaptation xsi:type="hintco:InterpolationAdaptation"/>
+      </ports>
+    </cosimunits>
+    <cosimunits xsi:type="hintco:CosimUnitInstance" identifier="msd2" declaration="//@csuDeclarations.1">
+      <ports xsi:type="hintco:InputPortInstance" identifier="x1" valueFrom="//@candidates.0/@cosimunits.0/@ports.0">
+        <adaptation xsi:type="hintco:ExtrapolationAdaptation"/>
+      </ports>
+      <ports xsi:type="hintco:InputPortInstance" identifier="v1" valueFrom="//@candidates.0/@cosimunits.0/@ports.1">
+        <adaptation xsi:type="hintco:ExtrapolationAdaptation"/>
+      </ports>
+      <ports xsi:type="hintco:OutputPortInstance" identifier="fk" valueTo="//@candidates.0/@cosimunits.0/@ports.2"/>
+      <ports xsi:type="hintco:OutputPortInstance" identifier="x2"/>
+      <ports xsi:type="hintco:OutputPortInstance" identifier="v2"/>
+    </cosimunits>
+    <cosimunits xsi:type="hintco:CosimUnitInstance" identifier="solution" declaration="//@csuDeclarations.2">
+      <ports xsi:type="hintco:OutputPortInstance" identifier="x1"/>
+      <ports xsi:type="hintco:OutputPortInstance" identifier="v1"/>
+      <ports xsi:type="hintco:OutputPortInstance" identifier="fk"/>
+      <ports xsi:type="hintco:OutputPortInstance" identifier="x2"/>
+      <ports xsi:type="hintco:OutputPortInstance" identifier="v2"/>
+    </cosimunits>
+  </candidates>
+  <csuDeclarations identifier="MassSpringDamper1" path="resources/msd/MassSpringDamper1.fmu" guid="{b9a6f0c4-d497-4270-82bd-b3082ab8b5ed}"/>
+  <csuDeclarations identifier="MassSpringDamper2" path="resources/msd/MassSpringDamper2.fmu" guid="{ae52e3f4-d126-465f-b9d8-691742b11bda}"/>
+  <csuDeclarations identifier="MassSpringDampersScenario" path="resources/msd/MassSpringDampersScenario.fmu" guid="{0b12379b-ea7f-4063-9a10-69708745f5e0}"/>
+</hintco:HintConfiguration>

+ 5 - 13
HintCOEngine/src/ua/ansymo/hintco/AdaptedFMUInstance.xtend

@@ -2,23 +2,13 @@ package ua.ansymo.hintco
 
 import java.util.List
 import java.util.Map
+import java.util.Set
 import org.eclipse.core.runtime.Assert
 import org.intocps.fmi.Fmi2Status
 import org.intocps.fmi.FmuInvocationException
 import org.intocps.fmi.IFmiComponent
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
-import ua.ansymo.hintco.ApproximationAdaptation
-import ua.ansymo.hintco.ExtrapolationAdaptation
-import ua.ansymo.hintco.InputPortInstance
-import ua.ansymo.hintco.InterpolationAdaptation
-import ua.ansymo.hintco.MultiRateAdaptation
-import ua.ansymo.hintco.OutputPortInstance
-import ua.ansymo.hintco.PortInstance
-import ua.ansymo.hintco.PowerBondAdaptation
-import ua.ansymo.hintco.PrecendenceNode
-import ua.ansymo.hintco.Scenario
-import ua.ansymo.hintco.UnitInstance
 
 enum MODE {
   NOT_INIT,
@@ -254,9 +244,11 @@ class AdaptedFMUInstance extends FmuInstance {
 		super.getReal(portName)
 	}
 
-	override outputSnapshot(Map<OutputPortInstance, Double> outVals) {
+	override outputSnapshot(Map<OutputPortInstance, Double> outVals, Set<OutputPortInstance> filter) {
 		for (outP : outputPorts){
-			outVals.put(outP, getReal(outP.identifier))
+			if (filter===null || filter.contains(outP)){
+				outVals.put(outP, getReal(outP.identifier))
+			}
 		}
 	}
 	

+ 3 - 3
HintCOEngine/src/ua/ansymo/hintco/AdaptiveCosimRunner.xtend

@@ -30,9 +30,9 @@ class AdaptiveCosimRunner implements ICosimRunner, Closeable {
 		
 		val nodes = scenarioNodesMap.get(rootScenario)
 		
-		if (nodes.filter(InputPortInstance).filter[it.isInput].exists[it.adaptation instanceof WaveformInterpolationAdaptation]){
-			logger.debug("AdaptiveCosimRunner using waveform relaxation.")
-			this.internalRunner = new WaveformCosimRunner(outProcessor, fmuLoader)
+		if (nodes.filter(InputPortInstance).filter[it.isInput].exists[it.adaptation instanceof RollbackInterpolationAdaptation]){
+			logger.debug("AdaptiveCosimRunner using string coupling.")
+			this.internalRunner = new StrongCouplingRunner(outProcessor, fmuLoader)
 		} else {
 			logger.debug("AdaptiveCosimRunner using single runner.")
 			this.internalRunner = new SingleCosimRunner(outProcessor, fmuLoader)

+ 44 - 21
HintCOEngine/src/ua/ansymo/hintco/CosimRunUtils.xtend

@@ -1,5 +1,6 @@
 package ua.ansymo.hintco
 
+import java.util.HashMap
 import java.util.List
 import java.util.Map
 import java.util.Set
@@ -7,11 +8,6 @@ import org.intocps.fmi.Fmi2Status
 import org.intocps.fmi.FmuInvocationException
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
-import ua.ansymo.hintco.InputPortInstance
-import ua.ansymo.hintco.OutputPortInstance
-import ua.ansymo.hintco.PrecendenceNode
-import ua.ansymo.hintco.Scenario
-import ua.ansymo.hintco.UnitInstance
 
 class CosimRunUtils {
 	
@@ -60,15 +56,18 @@ class CosimRunUtils {
 		}
 	}
 	
-	def static getOutputSnapshot(List<UnitInstance> units, Map<UnitInstance, IFmuInstance> fmuInstanceMap, Map<OutputPortInstance, Double> outVals) {
-		logger.debug("Getting output snapshot.")
+	def static getOutputSnapshot(List<UnitInstance> units, Map<UnitInstance, IFmuInstance> fmuInstanceMap, 
+											Map<OutputPortInstance, Double> outVals,
+											Set<OutputPortInstance> filter
+	){
+		logger.debug("Getting filtered output snapshot: {}", filter)
 		for (u : units) {
 			val fmu = fmuInstanceMap.get(u)
-			fmu.outputSnapshot(outVals)
+			fmu.outputSnapshot(outVals, filter)
 		}
 		return outVals
 	}
-	
+		
 	def static freeUnits(List<UnitInstance> units, Map<UnitInstance, IFmuInstance> fmuInstanceMap) {
 		for (unit : units) {
 			fmuInstanceMap.get(unit).free()
@@ -86,11 +85,12 @@ class CosimRunUtils {
 	def static fixPointOutputs(List<OutputPortInstance> childOutputPorts, Map<UnitInstance, IFmuInstance> fmuInstanceMap, int maxIt) {
 		// Fixpoint initial values, by following the same order of get.values as specified in nodes.
 		// Uses a map to store the previous value of each output. Then, whenever a new value of that output is obtained, checks if is the same as the previous value.
-		val outVals = newHashMap()
+		var outVals = newHashMap()
+		var prevOutVals = newHashMap()
 		
 		// Initialize
 		for (out : childOutputPorts){
-			outVals.put(out, Double.POSITIVE_INFINITY)
+			prevOutVals.put(out, Double.POSITIVE_INFINITY)
 			logger.debug("Output port {}.{} initialized to infinity", out.unit.identifier, out.identifier)
 		}
 		
@@ -100,15 +100,12 @@ class CosimRunUtils {
 		logger.debug("Starting fixed point iteration for time 0.0.")
 		while (!converged && i<maxIt){
 			logger.debug("Iteration {} start.", i)
-			converged = true
+			
 			for (out : childOutputPorts){
 				// TODO The getReal method of the hierarchical units needs to pull the latest value from the internal unit.
 				val outV = fmuInstanceMap.get(out.unit).getReal(out.identifier)
 				logger.debug("GetReal {}.{} at iteration {} yields {}.", out.unit.identifier, out.identifier, i, outV)
-				// Check if outV has changed.
-				if (! MathUtils.isApproximatelyEqual(outV, outVals.get(out), 1e-8)){
-					converged = false
-				}
+				
 				// Store the new value, for next iteration
 				outVals.put(out, outV)
 				
@@ -119,6 +116,14 @@ class CosimRunUtils {
 					logger.debug("SetReal {}.{} to {} at iteration {}.", trgPort.unit.identifier, trgPort.identifier, outV, i)
 				}
 			}
+			
+			converged = MathUtils.converged(outVals, prevOutVals, 1e-8)
+			
+			// Swap buffers
+			val aux = prevOutVals
+			prevOutVals = outVals
+			outVals = aux
+			
 			if (!converged){
 				logger.debug("Iteration {} end. Not Converged.", i)
 			} else {
@@ -130,14 +135,27 @@ class CosimRunUtils {
 		return outVals
 	}
 	
-	def static executeStep(Scenario scenario,  double time, double stepSize, List<PrecendenceNode> nodes, List<UnitInstance> units, Set<InputPortInstance> inPorts, Map<UnitInstance, IFmuInstance> fmuInstanceMap) {
+	def static executeStep(Scenario scenario,  double time, double stepSize, List<PrecendenceNode> nodes, 
+							List<UnitInstance> units, Set<InputPortInstance> inPorts, 
+							Map<UnitInstance, IFmuInstance> fmuInstanceMap,
+							HashMap<OutputPortInstance, Double> replayValues
+	) {
 		logger.debug("Co-simulation step of {} start at time {} -> {}.", scenario.identifier, time, time + stepSize)
 		
 		// Let units know that a cosim step is starting.
 		for (u : units){
+			logger.debug("Replay values {} at time {}.", replayValues, time)
 			fmuInstanceMap.get(u).startCosimStep(time, stepSize)
 		}
 		
+		// If there are values to replay, set them to the appropriate input ports.
+		if (replayValues !== null){
+			for (outP : replayValues.keySet){
+				val outV = replayValues.get(outP)
+				propagateValueToInputs(outV, outP, inPorts, fmuInstanceMap, time)
+			}
+		}
+		
 		for (n : nodes) {
 			switch (n) {
 				UnitInstance: {
@@ -151,10 +169,7 @@ class CosimRunUtils {
 					logger.debug("GetReal {}.{} at time {} yields {}.", n.unit.identifier, n.identifier, time, outV)
 					
 					// Propagate outV to the selected inputs (not all inputs need to be set because of Hierarchical units).
-					for (trgPort : n.valueTo.filter[inPorts.contains(it)]){
-						fmuInstanceMap.get(trgPort.unit).setReal(trgPort.identifier, outV)
-						logger.debug("SetReal {}.{} to {} at time {}.", trgPort.unit.identifier, trgPort.identifier, outV, time)
-					}
+					propagateValueToInputs(outV, n, inPorts, fmuInstanceMap, time)
 				}
 				default: {
 					// Do Nothing for InputPort instances.
@@ -169,4 +184,12 @@ class CosimRunUtils {
 		
 		logger.debug("Co-simulation step of {} end at time {} -> {}.", scenario.identifier, time, time + stepSize)
 	}
+	
+	def static propagateValueToInputs(double outV, OutputPortInstance n, Set<InputPortInstance> inPorts, Map<UnitInstance, IFmuInstance> fmuInstanceMap, double time) {
+		for (trgPort : n.valueTo.filter[inPorts.contains(it)]){
+			fmuInstanceMap.get(trgPort.unit).setReal(trgPort.identifier, outV)
+			logger.debug("SetReal {}.{} to {} at time {}.", trgPort.unit.identifier, trgPort.identifier, outV, time)
+		}
+	}
+	
 }

+ 4 - 4
HintCOEngine/src/ua/ansymo/hintco/HierarchicalInstance.xtend

@@ -122,13 +122,13 @@ class HierarchicalInstance extends AdaptedFMUInstance {
 		 * The input values have been propagated.
 		 * All we have to do now is step the inner units, and propagate their values in order.
 		 */
-		 executeStep(unit, inner_time, micro_step, nodes, units, inPorts, fmuInstanceMap)
+		 executeStep(unit, inner_time, micro_step, nodes, units, inPorts, fmuInstanceMap, null)
 		 return Fmi2Status.OK
 	}
 	
-	override outputSnapshot(Map<OutputPortInstance, Double> outVals) {
-		getOutputSnapshot(units, fmuInstanceMap, outVals)
-		super.outputSnapshot(outVals)
+	override outputSnapshot(Map<OutputPortInstance, Double> outVals, Set<OutputPortInstance> filter) {
+		getOutputSnapshot(units, fmuInstanceMap, outVals, filter)
+		super.outputSnapshot(outVals, filter)
 	}
 	
 	override free(){

+ 2 - 2
HintCOEngine/src/ua/ansymo/hintco/IFmuInstance.xtend

@@ -1,9 +1,9 @@
 package ua.ansymo.hintco
 
 import java.util.Map
+import java.util.Set
 import org.intocps.fmi.Fmi2Status
 import org.intocps.fmi.FmuInvocationException
-import ua.ansymo.hintco.OutputPortInstance
 
 interface IFmuInstance {
 	def Fmi2Status doStep(double currentCommunicationPoint, double communicationStepSize) throws FmuInvocationException
@@ -14,7 +14,7 @@ interface IFmuInstance {
 	def void setReal(String varName, double v)
 	def void free()	
 	def void reset()
-	def void outputSnapshot(Map<OutputPortInstance, Double> outVals)
+	def void outputSnapshot(Map<OutputPortInstance, Double> outVals, Set<OutputPortInstance> filter)
 	def void startCosimStep(double time, double stepSize)
 	def void endCosimStep(double time, double stepSize)
 	

+ 20 - 0
HintCOEngine/src/ua/ansymo/hintco/MathUtils.xtend

@@ -1,7 +1,27 @@
 package ua.ansymo.hintco
 
+import java.util.HashMap
+import org.eclipse.core.runtime.Assert
+
 class MathUtils {
 	def static isApproximatelyEqual(double d1, double d2, double delta) {
 		return d1 == d2 || delta > Math.abs(d1-d2) / Math.max(Math.abs(d1), Math.abs(d2));
 	}
+	
+	def static converged(HashMap<OutputPortInstance, Double> newOuts, HashMap<OutputPortInstance, Double> oldOuts, double tol) {
+		Assert.isTrue(newOuts !== null || oldOuts !== null)
+		if (newOuts === null || oldOuts === null) {
+			return false
+		}
+		for (outP : newOuts.keySet){
+			Assert.isTrue(oldOuts.containsKey(outP))
+			val nV = newOuts.get(outP)
+			val oV = oldOuts.get(outP)
+			if (! MathUtils.isApproximatelyEqual(oV, nV, tol)){
+				return false
+			}
+		}
+		return true
+	}
+	
 }

+ 5 - 7
HintCOEngine/src/ua/ansymo/hintco/SingleCosimRunner.xtend

@@ -11,12 +11,8 @@ import static ua.ansymo.hintco.CosimRunUtils.*
 
 class SingleCosimRunner implements ICosimRunner {
 	
-	protected Map<UnitInstance, IFmuInstance> fmuInstanceMap = newHashMap()
-	
 	protected IOutputProcessor outProcessor
-	
 	protected Logger logger = LoggerFactory.getLogger(typeof(SingleCosimRunner))
-	
 	protected IFmuLoader fmuLoader
 	
 	new (IOutputProcessor o, IFmuLoader l){
@@ -33,6 +29,8 @@ class SingleCosimRunner implements ICosimRunner {
 		
 		val outVals = newHashMap() // Will store output values
 		
+		val Map<UnitInstance, IFmuInstance> fmuInstanceMap = newHashMap()
+		
 		// Instantiate each unit, and record the instance reference.
 		instantiateUnits(units, fmuInstanceMap, fmuLoader, scenarioNodesMap)
 		
@@ -54,7 +52,7 @@ class SingleCosimRunner implements ICosimRunner {
 		
 		// Add first line
 		outVals.clear()
-		outProcessor.setOutputs(0, 0.0, getOutputSnapshot(units, fmuInstanceMap, outVals))
+		outProcessor.setOutputs(0, 0.0, getOutputSnapshot(units, fmuInstanceMap, outVals, null))
 		logger.debug("Output line recorded for time {}.", 0.0)
 		
 		exitInitMode(units, fmuInstanceMap)
@@ -66,7 +64,7 @@ class SingleCosimRunner implements ICosimRunner {
 		var i = 0
 		while (time < stopTime || MathUtils.isApproximatelyEqual(time, stopTime, stepSize*1e-3)){
 			
-			executeStep(rootScenario, time, stepSize, nodes, units, childInputPorts, fmuInstanceMap)
+			executeStep(rootScenario, time, stepSize, nodes, units, childInputPorts, fmuInstanceMap, null)
 			
 			// Advance time
 			logger.debug("Time advance: {} -> {}", time, time + stepSize)
@@ -80,7 +78,7 @@ class SingleCosimRunner implements ICosimRunner {
 			
 			// Refresh outputs
 			outVals.clear()
-			outProcessor.setOutputs(i, time, getOutputSnapshot(units, fmuInstanceMap, outVals))
+			outProcessor.setOutputs(i, time, getOutputSnapshot(units, fmuInstanceMap, outVals, null))
 			logger.debug("Output line recorded for time {} and step {}.", time, i)
 		}
 		

+ 270 - 0
HintCOEngine/src/ua/ansymo/hintco/StrongCouplingRunner.xtend

@@ -0,0 +1,270 @@
+package ua.ansymo.hintco
+
+import java.io.IOException
+import java.util.HashMap
+import java.util.List
+import java.util.Map
+import java.util.Set
+import org.eclipse.core.runtime.Assert
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+import static ua.ansymo.hintco.CosimRunUtils.*
+
+class StrongCouplingRunner implements ICosimRunner {
+	/*
+	 * Runs the string coupling version of the co-simulation.
+	 * Does not require FMUs to be able to rollback, but extremely inefficient
+	 * 
+	  Algorithm:
+		1. Initialize cosim and record the first outputs.
+		2. Set step=1
+		3. Count total steps
+		4. while step<=total_steps:
+		4.1 Converge for step.
+		4.2 step=step+1
+		5. Output cosim results
+		
+	  Converge for step
+		1. Given a step > 0
+		2. Let iteration counter i=0
+		3. Assume that all fmus are reset.
+		4. Run each FMU up to step-1
+		5. If i>0 set the previously recorded outputs to the rollback inputs
+		6. Run the cosim step.
+		7. Get new outputs, and check for convergence
+		8. i = i + 1 and reset fmus
+		9. If no convergence, go to 3.
+		10. Otherwise return true unless max iterations has exceeded.
+		11. Otherwise, issue warning and ?
+	 */
+	
+	protected IOutputProcessor outProcessor
+	protected Logger logger = LoggerFactory.getLogger(typeof(StrongCouplingRunner))
+	protected IFmuLoader fmuLoader
+	
+	new (IOutputProcessor o, IFmuLoader l){
+		outProcessor = o
+		fmuLoader = 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
+		val rollbackOutputPorts = nodes.filter(InputPortInstance).filter[it.isInput]
+										.filter[p | p.adaptation instanceof RollbackInterpolationAdaptation]
+										.map[p | p.valueFrom]
+										.toSet
+		
+		val Map<UnitInstance, IFmuInstance> fmuInstanceMap = newHashMap()
+
+		// Instantiate each unit, and record the instance reference.
+		instantiateUnits(units, fmuInstanceMap, fmuLoader, scenarioNodesMap)
+		
+		val totalCosimSteps = Math.toIntExact(Math.round(rootScenario.stopTime/rootScenario.stepSize)+2)
+		
+		val HashMap<OutputPortInstance, Double>[] cosimSignals = newArrayOfSize(totalCosimSteps)
+		
+		var stepToConverge = 0
+		
+		while (stepToConverge < totalCosimSteps){
+			logger.debug("String coupling iteration for step {} started.", stepToConverge)
+			
+			// run co-simulation up to step convergedStep and converge a new step.
+			convergeStep(stepToConverge, units, fmuInstanceMap, childInputPorts, 
+						childOutputPorts, rollbackOutputPorts, cosimSignals, rootScenario,
+						nodes)
+			
+			logger.debug("String coupling iteration for step {} terminated.", stepToConverge)
+			stepToConverge=stepToConverge+1		
+		}
+		
+		Assert.isTrue(cosimSignals.get(totalCosimSteps-1) !== null)
+		
+		// Produce outputs
+		printFinalOutputs(variantID, units, fmuInstanceMap, childInputPorts, 
+						childOutputPorts, rollbackOutputPorts, cosimSignals, rootScenario,
+						nodes)
+		
+		logger.debug("Outputs produced.")
+		
+		freeUnits(units, fmuInstanceMap)
+		
+		logger.debug("Cleanup complete.")
+	}
+	
+	def convergeStep(int stepToConverge, List<UnitInstance> units, 
+						Map<UnitInstance, IFmuInstance> fmuInstanceMap,
+						Set<InputPortInstance> childInputPorts,
+						List<OutputPortInstance> childOutputPorts, 
+						Set<OutputPortInstance> rollbackOutputPorts, 
+						HashMap<OutputPortInstance, Double>[] cosimSignals,
+						RootCandidateScenario rootScenario,
+						List<PrecendenceNode> nodes
+	) {
+		if (stepToConverge == 0){
+			// Initialize cosim and record first outputs.
+			
+			// setup experiment
+			setupExperiment(units, fmuInstanceMap)
+			
+			// Go into init mode
+			intoInitMode(units, fmuInstanceMap)
+			
+			fixPointOutputs(childOutputPorts, fmuInstanceMap, rootScenario.maxInitIterations)
+			
+			// record first line for outputs that are to be used in the convergence
+			val outVals = newHashMap() 
+			getOutputSnapshot(units, fmuInstanceMap, outVals, rollbackOutputPorts)
+			cosimSignals.set(0,outVals as HashMap<OutputPortInstance, Double>)
+			logger.debug("Output line recorded for time {} and step {}.", 0.0, 0.0)
+			
+			exitInitMode(units, fmuInstanceMap)
+			resetUnits(units, fmuInstanceMap)
+		} else {
+			val convergedStep = stepToConverge-1
+			
+			val maxCosims = rootScenario.maxInitIterations
+			var cosimCounter = 0
+			var hasConverged = false
+			
+			while (!hasConverged && cosimCounter<maxCosims){
+				logger.debug("Cosim {} started.", cosimCounter)
+				
+				val time = initializeFMUsToStep(convergedStep, units, fmuInstanceMap, childInputPorts, childOutputPorts, 
+									cosimSignals, rootScenario, nodes)
+				
+				hasConverged = runNewCosimStep(time, convergedStep, cosimCounter, units, 
+												fmuInstanceMap,
+												childInputPorts, childOutputPorts, rollbackOutputPorts,
+												cosimSignals,
+												rootScenario,
+												nodes)
+				
+				resetUnits(units, fmuInstanceMap)
+				
+				cosimCounter = cosimCounter + 1
+				
+				logger.debug("Cosim {} ended.", cosimCounter)
+			}
+			
+			if (cosimCounter >= maxCosims){
+				logger.warn("Convergence of cosim step {} exceeded max iteration ({})", stepToConverge, maxCosims)
+			}
+		}
+		logger.debug("Step {} converged.", stepToConverge)
+	}
+	
+	def runNewCosimStep(double time, int convergedStep, int cosimCounter, 
+						List<UnitInstance> units, 
+						Map<UnitInstance, IFmuInstance> fmuInstanceMap,
+						Set<InputPortInstance> childInputPorts,
+						List<OutputPortInstance> childOutputPorts,
+						Set<OutputPortInstance> rollbackOutputPorts,
+						HashMap<OutputPortInstance, Double>[] cosimSignals,
+						RootCandidateScenario rootScenario,
+						List<PrecendenceNode> nodes) {
+		val stepSize = rootScenario.stepSize
+		
+		val nonConvergedStep = convergedStep+1
+		
+		val replayValues = cosimSignals.get(nonConvergedStep)
+		
+		// Note that replayValues might be null.
+		executeStep(rootScenario, time, stepSize, nodes, units, childInputPorts, fmuInstanceMap, replayValues)
+		
+		val newReplayValues = newHashMap() 
+		getOutputSnapshot(units, fmuInstanceMap, newReplayValues, rollbackOutputPorts)
+		
+		// Store the new values for the next iteration
+		cosimSignals.set(nonConvergedStep, newReplayValues)
+		
+		return MathUtils.converged(newReplayValues, replayValues, 1e-8)
+	}
+	
+	def initializeFMUsToStep(int convergedStep, List<UnitInstance> units, 
+						Map<UnitInstance, IFmuInstance> fmuInstanceMap,
+						Set<InputPortInstance> childInputPorts,
+						List<OutputPortInstance> childOutputPorts,
+						HashMap<OutputPortInstance, Double>[] cosimSignals,
+						RootCandidateScenario rootScenario,
+						List<PrecendenceNode> nodes) {
+		
+		setupExperiment(units, fmuInstanceMap)
+		intoInitMode(units, fmuInstanceMap)
+		fixPointOutputs(childOutputPorts, fmuInstanceMap, rootScenario.maxInitIterations)
+		exitInitMode(units, fmuInstanceMap)
+		
+		var i = 0
+		var time = 0.0d
+		val stepSize = rootScenario.stepSize
+		
+		while (i < convergedStep){
+			val replayValues = cosimSignals.get(i+1)
+			Assert.isTrue(replayValues !== null)
+			executeStep(rootScenario, time, stepSize, nodes, units, childInputPorts, fmuInstanceMap, replayValues)
+			time = time + stepSize
+			i = i + 1
+		}
+		
+		return time // Fmus are ready to run the convergedStep+1
+	}
+	
+	def printFinalOutputs(String variantID, List<UnitInstance> units, 
+						Map<UnitInstance, IFmuInstance> fmuInstanceMap,
+						Set<InputPortInstance> childInputPorts,
+						List<OutputPortInstance> childOutputPorts, 
+						Set<OutputPortInstance> rollbackOutputPorts, 
+						HashMap<OutputPortInstance, Double>[] cosimSignals,
+						RootCandidateScenario rootScenario,
+						List<PrecendenceNode> nodes) {
+		// Since we stored only the output that needed to be replayed, in order to plot the final outputs we need to run a final co-sim.
+		
+		val outVals = newHashMap()
+		
+		setupExperiment(units, fmuInstanceMap)
+		intoInitMode(units, fmuInstanceMap)
+		
+		fixPointOutputs(childOutputPorts, fmuInstanceMap, rootScenario.maxInitIterations)
+		
+		outProcessor.initialize(rootScenario, variantID)
+		
+		outProcessor.setOutputs(0, 0.0, getOutputSnapshot(units, fmuInstanceMap, outVals, null))
+		
+		exitInitMode(units, fmuInstanceMap)
+		
+		val stepSize = rootScenario.stepSize
+		val totalCosimSteps = cosimSignals.length
+		var i = 1
+		var time = 0.0
+		
+		while (i < totalCosimSteps){
+			val replayValues = cosimSignals.get(i)
+			
+			Assert.isTrue(replayValues !== null)
+			executeStep(rootScenario, time, stepSize, nodes, units, childInputPorts, fmuInstanceMap, replayValues)
+			
+			outVals.clear()
+			time = time + stepSize
+			outProcessor.setOutputs(i, time, getOutputSnapshot(units, fmuInstanceMap, outVals, null))
+			i = i + 1
+		}
+		
+		val stopTime = rootScenario.stopTime
+		Assert.isTrue(MathUtils.isApproximatelyEqual(time-stepSize, stopTime, stepSize*1e-3))
+		
+		outProcessor.terminate()
+	}
+	
+	override close() throws IOException {
+		fmuLoader.free()
+	}
+	
+}

+ 0 - 250
HintCOEngine/src/ua/ansymo/hintco/WaveformCosimRunner.xtend

@@ -1,250 +0,0 @@
-package ua.ansymo.hintco
-
-import java.util.HashMap
-import java.util.List
-import java.util.Map
-import org.eclipse.core.runtime.Assert
-import ua.ansymo.hintco.InputPortInstance
-import ua.ansymo.hintco.OutputPortInstance
-import ua.ansymo.hintco.PrecendenceNode
-import ua.ansymo.hintco.RootCandidateScenario
-import ua.ansymo.hintco.Scenario
-import ua.ansymo.hintco.UnitInstance
-
-import static ua.ansymo.hintco.CosimRunUtils.*
-
-class WaveformCosimRunner extends SingleCosimRunner {
-	/*
-	 * 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 guess. 
-		{
-			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)
-			
-			fixPointOutputs(childOutputPorts, fmuInstanceMap, rootScenario.maxInitIterations)
-			
-			// 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 = 20
-		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.")
-	}
-	
-}

+ 0 - 18
HintCOEngine/test/ua/ansymo/hintco/test/CandidatesGeneratorTest.xtend

@@ -225,24 +225,6 @@ class CandidatesGeneratorTest {
 		assertEquals(1, variants.size)
 	}
 	
-	@Test
-	def void generateWaveformVariantsTest(){
-		val src = loader.loadCandidates("instances/waveform_interpolation_test.hintco")
-		val variants = newLinkedList()
-		val generator = new CandidatesGenerator(new ConstraintChecker(), new VariantValidator(),
-			[ns, vId, constraints, cs |
-			variants.add(vId)
-			val scenario = ModelQuery.findRootScenario(ns)
-			val orderMap = new VariantProcessor(null).computeOperationOrder(scenario, ns, cs)
-			val order = orderMap.values.head
-			
-			assertTrue(comesBefore("A", "u1", order, cs))
-		])
-		generator.createVariantTree(src)
-		generator.generateVariants(src)
-		assertEquals(1, variants.size)
-	}
-	
 	@Test
 	def void generateLimitedVariantsTest(){
 		val src = loader.loadCandidates("instances/generate_variants_test.xmi")

+ 9 - 3
HintCOEngine/test/ua/ansymo/hintco/test/WvFormCosimRunnerTest.xtend

@@ -13,14 +13,20 @@ import ua.ansymo.hintco.VariantValidator
 class WvFormCosimRunnerTest {
 	@Test
 	def void executeWVCosimMSD(){
-		val resultsDirPath = "results-gen/waveformMSDTest"
 		val loader = new ModelStorage()
-		val src = loader.loadCandidates("instances/waveform_msd.hintco")
 		
-		val runner = new AdaptiveCosimRunner(new OutputProcessor(resultsDirPath), new FmuLoader)
+		val src = loader.loadCandidates("instances/waveform_msd.hintco")
+		val runner = new AdaptiveCosimRunner(new OutputProcessor("results-gen/strongCoupMSDTest/Strong"), new FmuLoader)
 		var generator = new CandidatesGenerator(new ConstraintChecker,new VariantValidator, new VariantProcessor(runner))
 		generator.createVariantTree(src)
 		generator.generateVariants(src, 1)
 		runner.close()
+		
+		val normal_src = loader.loadCandidates("instances/waveform_msd_normal.hintco")
+		val normal_runner = new AdaptiveCosimRunner(new OutputProcessor("results-gen/strongCoupMSDTest/Normal"), new FmuLoader)
+		var normal_generator = new CandidatesGenerator(new ConstraintChecker,new VariantValidator, new VariantProcessor(normal_runner))
+		normal_generator.createVariantTree(normal_src)
+		normal_generator.generateVariants(normal_src, 1)
+		normal_runner.close()
 	}
 }