Преглед изворни кода

implemented naive waveform relaxation, but failing.

Claudio Gomes пре 6 година
родитељ
комит
6a7a2fee1f
29 измењених фајлова са 6541 додато и 9 уклоњено
  1. 49 0
      HintCO/instances/elevator_load_scenario.hintco
  2. 73 0
      HintCO/instances/waveform_watertanks.xmi
  3. BIN
      HintCO/resources/elevator/Controller_FMU.fmu
  4. BIN
      HintCO/resources/elevator/Elevator_FMU.fmu
  5. BIN
      HintCO/resources/elevator/Motor_FMU.fmu
  6. BIN
      HintCO/resources/elevator/PSU_FMU.fmu
  7. BIN
      HintCO/resources/elevator/Transmission_FMU.fmu
  8. BIN
      HintCO/resources/elevator/load_FMU.fmu
  9. 1002 0
      HintCO/resources/elevator/solution/ctrl.csv
  10. BIN
      HintCO/resources/elevator/solution/ctrl.xlsx
  11. 1002 0
      HintCO/resources/elevator/solution/elevator.csv
  12. BIN
      HintCO/resources/elevator/solution/elevator.xlsx
  13. 1002 0
      HintCO/resources/elevator/solution/load.csv
  14. BIN
      HintCO/resources/elevator/solution/load.xlsx
  15. 1002 0
      HintCO/resources/elevator/solution/motor.csv
  16. BIN
      HintCO/resources/elevator/solution/motor.xlsx
  17. 1002 0
      HintCO/resources/elevator/solution/psu.csv
  18. BIN
      HintCO/resources/elevator/solution/psu.xlsx
  19. 1002 0
      HintCO/resources/elevator/solution/trans.csv
  20. BIN
      HintCO/resources/elevator/solution/trans.xlsx
  21. 73 4
      HintCO/scripts/plot_results.py
  22. 7 0
      HintCO/src/ua/ansymo/hintco/CosimRunUtils.xtend
  23. 4 5
      HintCO/src/ua/ansymo/hintco/CosimRunner.xtend
  24. 4 0
      HintCO/src/ua/ansymo/hintco/FmuInstance.xtend
  25. 4 0
      HintCO/src/ua/ansymo/hintco/HierarchicalInstance.xtend
  26. 1 0
      HintCO/src/ua/ansymo/hintco/IFmuInstance.xtend
  27. 246 0
      HintCO/src/ua/ansymo/hintco/WaveformCosimRunner.xtend
  28. 17 0
      HintCO/test/ua/ansymo/hintco/test/CosimRunnerTest.xtend
  29. 51 0
      HintCO/test/ua/ansymo/hintco/test/WvFormCosimRunnerTest.xtend

+ 49 - 0
HintCO/instances/elevator_load_scenario.hintco

@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="ASCII"?>
+<hintco:Candidates 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="10.0" stepSize="0.001" outputStepSize="0.01">
+    <cosimunits xsi:type="hintco:CosimUnitInstance" identifier="load" declaration="//@csuDeclarations.0">
+      <ports xsi:type="hintco:OutputPortInstance" identifier="T_load@expseu_" valueTo="//@candidates.0/@cosimunits.1/@ports.2"/>
+    </cosimunits>
+    <cosimunits xsi:type="hintco:CosimUnitInstance" identifier="elevator" declaration="//@csuDeclarations.1">
+      <ports xsi:type="hintco:OutputPortInstance" identifier="x_shaft@expseu_" valueTo="//@candidates.0/@cosimunits.2/@ports.5"/>
+      <ports xsi:type="hintco:OutputPortInstance" identifier="v_shaft@expseu_" valueTo="//@candidates.0/@cosimunits.2/@ports.6"/>
+      <ports xsi:type="hintco:InputPortInstance" identifier="T_load@expseu_" valueFrom="//@candidates.0/@cosimunits.0/@ports.0"/>
+      <ports xsi:type="hintco:InputPortInstance" identifier="F_shaft@expseu_" valueFrom="//@candidates.0/@cosimunits.2/@ports.2"/>
+    </cosimunits>
+    <cosimunits xsi:type="hintco:CosimUnitInstance" identifier="trans" declaration="//@csuDeclarations.2">
+      <ports xsi:type="hintco:OutputPortInstance" identifier="x_measure@expseu_" valueTo="//@candidates.0/@cosimunits.3/@ports.1"/>
+      <ports xsi:type="hintco:OutputPortInstance" identifier="omega_measure@expseu_" valueTo="//@candidates.0/@cosimunits.3/@ports.2"/>
+      <ports xsi:type="hintco:OutputPortInstance" identifier="F_shaft@expseu_" valueTo="//@candidates.0/@cosimunits.1/@ports.3"/>
+      <ports xsi:type="hintco:OutputPortInstance" identifier="omega_shaft@expseu_" valueTo="//@candidates.0/@cosimunits.4/@ports.3"/>
+      <ports xsi:type="hintco:InputPortInstance" identifier="T_shaft@expseu_" valueFrom="//@candidates.0/@cosimunits.4/@ports.2"/>
+      <ports xsi:type="hintco:InputPortInstance" identifier="X_shaft@expseu_" valueFrom="//@candidates.0/@cosimunits.1/@ports.0"/>
+      <ports xsi:type="hintco:InputPortInstance" identifier="V_shaft@expseu_" valueFrom="//@candidates.0/@cosimunits.1/@ports.1"/>
+    </cosimunits>
+    <cosimunits xsi:type="hintco:CosimUnitInstance" identifier="ctrl" declaration="//@csuDeclarations.3">
+      <ports xsi:type="hintco:OutputPortInstance" identifier="T_cmd@expseu_" valueTo="//@candidates.0/@cosimunits.4/@ports.4 //@candidates.0/@cosimunits.4/@ports.6"/>
+      <ports xsi:type="hintco:InputPortInstance" identifier="Speed@expseu_" valueFrom="//@candidates.0/@cosimunits.2/@ports.0"/>
+      <ports xsi:type="hintco:InputPortInstance" identifier="Position@expseu_" valueFrom="//@candidates.0/@cosimunits.2/@ports.1"/>
+    </cosimunits>
+    <cosimunits xsi:type="hintco:CosimUnitInstance" identifier="motor" declaration="//@csuDeclarations.4">
+      <ports xsi:type="hintco:OutputPortInstance" identifier="I_low@expseu_" valueTo="//@candidates.0/@cosimunits.5/@ports.2"/>
+      <ports xsi:type="hintco:OutputPortInstance" identifier="I_high@expseu_" valueTo="//@candidates.0/@cosimunits.5/@ports.3"/>
+      <ports xsi:type="hintco:OutputPortInstance" identifier="T_shaft@expseu_" valueTo="//@candidates.0/@cosimunits.2/@ports.4"/>
+      <ports xsi:type="hintco:InputPortInstance" identifier="Omega_shaft@expseu_" valueFrom="//@candidates.0/@cosimunits.2/@ports.3"/>
+      <ports xsi:type="hintco:InputPortInstance" identifier="Tcmd@expseu_" valueFrom="//@candidates.0/@cosimunits.3/@ports.0"/>
+      <ports xsi:type="hintco:InputPortInstance" identifier="U_high@expseu_" valueFrom="//@candidates.0/@cosimunits.5/@ports.1"/>
+      <ports xsi:type="hintco:InputPortInstance" identifier="Tcmd@expseu_" valueFrom="//@candidates.0/@cosimunits.3/@ports.0"/>
+    </cosimunits>
+    <cosimunits xsi:type="hintco:CosimUnitInstance" identifier="psu" declaration="//@csuDeclarations.5">
+      <ports xsi:type="hintco:OutputPortInstance" identifier="U_low@expseu_"/>
+      <ports xsi:type="hintco:OutputPortInstance" identifier="U_high@expseu_" valueTo="//@candidates.0/@cosimunits.4/@ports.5"/>
+      <ports xsi:type="hintco:InputPortInstance" identifier="I_low@expseu_" valueFrom="//@candidates.0/@cosimunits.4/@ports.0"/>
+      <ports xsi:type="hintco:InputPortInstance" identifier="I_high@expseu_" valueFrom="//@candidates.0/@cosimunits.4/@ports.1"/>
+    </cosimunits>
+  </candidates>
+  <csuDeclarations identifier="load_FMU" path="resources/elevator/load_FMU.fmu" guid="{25941611-1350-4ca6-8fe8-85d9193c14fd}"/>
+  <csuDeclarations identifier="Elevator_FMU" path="resources/elevator/Elevator_FMU.fmu" guid="{25941611-1350-4ca5-8fe8-85d9193c14fd}"/>
+  <csuDeclarations identifier="Transmission_FMU" path="resources/elevator/Transmission_FMU.fmu" guid="{25941611-1350-4ca3-8fe8-85d9193c14fd}"/>
+  <csuDeclarations identifier="Controller_FMU" path="resources/elevator/Controller_FMU.fmu" guid="{25941611-1350-4ca4-8fe8-85d9193c14fd}"/>
+  <csuDeclarations identifier="Motor_FMU" path="resources/elevator/Motor_FMU.fmu" guid="{25941611-1350-4ca7-8fe8-85d9193c14fd}"/>
+  <csuDeclarations identifier="PSU_FMU" path="resources/elevator/PSU_FMU.fmu" guid="{25941611-1350-4ca8-8fe8-85d9193c14fd}"/>
+</hintco:Candidates>

+ 73 - 0
HintCO/instances/waveform_watertanks.xmi

@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="ASCII"?>
+<hintco:Candidates
+    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="15.0"
+      stepSize="0.1"
+      outputStepSize="0.1">
+    <cosimunits
+        xsi:type="hintco:CosimUnitInstance"
+        identifier="ctrl"
+        declaration="//@csuDeclarations.0">
+      <ports
+          xsi:type="hintco:InputPortInstance"
+          identifier="wt3_level"
+          valueFrom="//@candidates.0/@cosimunits.2/@ports.2"/>
+      <ports
+          xsi:type="hintco:OutputPortInstance"
+          identifier="wt3_valve"
+          valueTo="//@candidates.0/@cosimunits.2/@ports.4"/>
+    </cosimunits>
+    <cosimunits
+        xsi:type="hintco:CosimUnitInstance"
+        identifier="wt1"
+        declaration="//@csuDeclarations.1">
+      <ports
+          xsi:type="hintco:OutputPortInstance"
+          identifier="Tank2WaterLevel"/>
+      <ports
+          xsi:type="hintco:OutputPortInstance"
+          identifier="Tank2OutFlow"
+          valueTo="//@candidates.0/@cosimunits.2/@ports.3"/>
+    </cosimunits>
+    <cosimunits
+        xsi:type="hintco:CosimUnitInstance"
+        identifier="wt2"
+        declaration="//@csuDeclarations.2">
+      <ports
+          xsi:type="hintco:OutputPortInstance"
+          identifier="puddle"/>
+      <ports
+          xsi:type="hintco:OutputPortInstance"
+          identifier="Tank3OutFlow"/>
+      <ports
+          xsi:type="hintco:OutputPortInstance"
+          identifier="level"
+          valueTo="//@candidates.0/@cosimunits.0/@ports.0"/>
+      <ports
+          xsi:type="hintco:InputPortInstance"
+          identifier="inFlow"
+          valueFrom="//@candidates.0/@cosimunits.1/@ports.1"/>
+      <ports
+          xsi:type="hintco:InputPortInstance"
+          identifier="valveControl"
+          valueFrom="//@candidates.0/@cosimunits.0/@ports.1"/>
+    </cosimunits>
+  </candidates>
+  <csuDeclarations
+      identifier="threewatertankcontroller2"
+      path="resources/threewatertankcontroller2.fmu"
+      guid="{8c4e810f-3df3-4a00-8276-176fa3c9f003}"/>
+  <csuDeclarations
+      identifier="threewatertank1"
+      path="resources/threewatertank1.fmu"
+      guid="{dcd729ec-423a-4a0d-8030-4c42a840abba}"/>
+  <csuDeclarations
+      identifier="threewatertank2"
+      path="resources/threewatertank2.fmu"
+      guid="{25941611-1350-4ca2-8fe8-85d9193c14fd}"/>
+</hintco:Candidates>

BIN
HintCO/resources/elevator/Controller_FMU.fmu


BIN
HintCO/resources/elevator/Elevator_FMU.fmu


BIN
HintCO/resources/elevator/Motor_FMU.fmu


BIN
HintCO/resources/elevator/PSU_FMU.fmu


BIN
HintCO/resources/elevator/Transmission_FMU.fmu


BIN
HintCO/resources/elevator/load_FMU.fmu


Разлика између датотеке није приказан због своје велике величине
+ 1002 - 0
HintCO/resources/elevator/solution/ctrl.csv


BIN
HintCO/resources/elevator/solution/ctrl.xlsx


Разлика између датотеке није приказан због своје велике величине
+ 1002 - 0
HintCO/resources/elevator/solution/elevator.csv


BIN
HintCO/resources/elevator/solution/elevator.xlsx


Разлика између датотеке није приказан због своје велике величине
+ 1002 - 0
HintCO/resources/elevator/solution/load.csv


BIN
HintCO/resources/elevator/solution/load.xlsx


Разлика између датотеке није приказан због своје велике величине
+ 1002 - 0
HintCO/resources/elevator/solution/motor.csv


BIN
HintCO/resources/elevator/solution/motor.xlsx


Разлика између датотеке није приказан због своје велике величине
+ 1002 - 0
HintCO/resources/elevator/solution/psu.csv


BIN
HintCO/resources/elevator/solution/psu.xlsx


Разлика између датотеке није приказан због своје велике величине
+ 1002 - 0
HintCO/resources/elevator/solution/trans.csv


BIN
HintCO/resources/elevator/solution/trans.xlsx


+ 73 - 4
HintCO/scripts/plot_results.py

@@ -3,6 +3,7 @@ import os
 import sys
 import matplotlib.pyplot as plt
 from matplotlib.backends.backend_pdf import PdfPages
+import argparse
 
 def read_data(filepath):
 	results = {}
@@ -39,7 +40,7 @@ def get_all_csvs(results_folder):
                 filepath = os.fsdecode(os.path.join(root,file))
                 results.append((filename, filepath))
     return results
-            
+
 def plot_results(results_folder, analyticalSolutionDir):
     for (filename, filepath) in get_all_csvs(results_folder):
         # Read all data onto a dictionary
@@ -90,11 +91,79 @@ def plot_results(results_folder, analyticalSolutionDir):
         
         print("Done.")
 
+def smallest_identifier_string(strs):
+    # Taken from https://medium.com/@d_dchris/10-methods-to-solve-the-longest-common-prefix-problem-using-python-leetcode-14-a87bb3eb0f3a
+    longest_pre = ""
+    if not strs: 
+        return longest_pre
+    shortest_str = min(strs, key=len)
+    for i in range(len(shortest_str)):
+        if all([x.startswith(shortest_str[:i+1]) for x in strs]):
+            longest_pre = shortest_str[:i+1]
+        else:
+            break
+    return longest_pre
+  
+def plot_merge_results(results_folder):
+    # Get all files (and their path) ending in csv.
+    all_csv_files = get_all_csvs(results_folder)
+    
+    # Group them by filename. Eg., all fmu1.csv files should be together.
+    csv_files_grouped = {}
+    for (filename, filepath) in all_csv_files:
+        if not filename in csv_files_grouped:
+            csv_files_grouped[filename] = []
+        csv_files_grouped[filename].append(filepath)
+    
+    print("Files grouped.")
+    
+    # Create plot for each group
+    for (filename, files) in csv_files_grouped.items():
+        plot_filepath = files[0]
+        plot_file_name = plot_filepath.replace(".csv",".pdf")
+        pp = PdfPages(plot_file_name)
+        group_id = smallest_identifier_string(files)
+        
+        for filepath in files:
+            file_identifier = filepath[len(group_id):]
+            raw_data = read_data(filepath)
+            print("Read data from file",filename,".")
+            
+            num_trajectories = len(raw_data.keys()) - 1 # Exclude time 
+            
+            # Assumes that each csv column is in the same order on all csvs. This is a reasonable assumption...
+            plot_num = 1
+            for col in raw_data.keys():
+                if col != "time":
+                    print("Plotting col " + col + "...")
+                    plt.subplot(num_trajectories, 1, plot_num)
+                    plt.xlabel("time")
+                    plt.plot(raw_data["time"], raw_data[col], '-', label=file_identifier+"."+col)
+                    plt.legend()
+                    plot_num += 1
+        pp.savefig()
+        pp.close()
+        plt.clf()
+    
+    print("Done.")
+
 if __name__ == '__main__':
-    results_folder = sys.argv[1] # read results file
-    analyticalSolutionDir = sys.argv[2] if len(sys.argv)>2 else ""
+    parser = argparse.ArgumentParser(description='HintCO plotting utility')
+    parser.add_argument('resultsdir', help='directory where csv files of the co-simulation are stored.')
+    parser.add_argument('--solutionsdir', help='directory where csv files of the analytical solution are stored.')
+    parser.add_argument('--merge', action="store_true", help='Instead of creating a plot per folder, merge all plots into a single folder. This requires a fixed folder structure')
+    
+    args = parser.parse_args()
+    
+    results_folder = args.resultsdir
+    analyticalSolutionDir = args.solutionsdir if args.solutionsdir else ""
+    merge = args.merge
     print("results=",results_folder)
     print("analyticalSolutionDir=",analyticalSolutionDir)
-    plot_results(results_folder, analyticalSolutionDir)
+    print("merge=",merge)
+    if merge:
+        plot_merge_results(results_folder)
+    else:
+        plot_results(results_folder, analyticalSolutionDir)
     
 

+ 7 - 0
HintCO/src/ua/ansymo/hintco/CosimRunUtils.xtend

@@ -71,6 +71,13 @@ class CosimRunUtils {
 		}
 	}
 	
+	def static resetUnits(List<UnitInstance> units, Map<UnitInstance, IFmuInstance> fmuInstanceMap) {
+		for (unit : units) {
+			fmuInstanceMap.get(unit).reset()
+			logger.debug("Unit {} free.", unit)
+		}
+	}
+	
 	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.

+ 4 - 5
HintCO/src/ua/ansymo/hintco/CosimRunner.xtend

@@ -12,13 +12,13 @@ import static ua.ansymo.hintco.CosimRunUtils.*
 
 class CosimRunner implements ICosimRunner, Closeable {
 	
-	val Map<UnitInstance, IFmuInstance> fmuInstanceMap = newHashMap()
+	protected Map<UnitInstance, IFmuInstance> fmuInstanceMap = newHashMap()
 	
-	IOutputProcessor outProcessor
+	protected IOutputProcessor outProcessor
 	
-	Logger logger = LoggerFactory.getLogger(typeof(CosimRunner))
+	protected Logger logger = LoggerFactory.getLogger(typeof(CosimRunner))
 	
-	IFmuLoader fmuLoader
+	protected IFmuLoader fmuLoader
 	
 	new (IOutputProcessor o, IFmuLoader l){
 		outProcessor = o
@@ -80,7 +80,6 @@ class CosimRunner implements ICosimRunner, Closeable {
 			// 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.
-			// If traceOutput is enabled, we print every value set to the fmu, and the time at which it was set. 
 			
 			// Refresh outputs
 			outVals.clear()

+ 4 - 0
HintCO/src/ua/ansymo/hintco/FmuInstance.xtend

@@ -66,4 +66,8 @@ abstract class FmuInstance implements IFmuInstance  {
 		f.freeInstance()
 	}
 	
+	override reset(){
+		f.reset()
+	}
+	
 }

+ 4 - 0
HintCO/src/ua/ansymo/hintco/HierarchicalInstance.xtend

@@ -126,4 +126,8 @@ class HierarchicalInstance extends AdaptedFMUInstance {
 		freeUnits(units, fmuInstanceMap)
 	}
 	
+	override reset() {
+		resetUnits(units, fmuInstanceMap)
+	}
+	
 }

+ 1 - 0
HintCO/src/ua/ansymo/hintco/IFmuInstance.xtend

@@ -12,6 +12,7 @@ interface IFmuInstance {
 	def double getReal(String varName)
 	def void setReal(String varName, double v)
 	def void free()	
+	def void reset()
 	def void outputSnapshot(Map<OutputPortInstance, Double> outVals)
 	def void startCosimStep(double time, double stepSize)
 	def void endCosimStep(double time, double stepSize)

+ 246 - 0
HintCO/src/ua/ansymo/hintco/WaveformCosimRunner.xtend

@@ -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.")
+	}
+	
+}

+ 17 - 0
HintCO/test/ua/ansymo/hintco/test/CosimRunnerTest.xtend

@@ -307,4 +307,21 @@ class CosimRunnerTest {
 		assertTrue(resultsWt2.exists)
 	}
 	
+	@Test
+	@Ignore // Only works if you have amesim license.
+	def void executeCosimulationElevatorLoad(){
+		Assume.assumeTrue(SystemUtils.IS_OS_WINDOWS) // Because it only works on windows.
+//		System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "debug")
+//		System.setProperty("org.slf4j.simpleLogger.logFile", "powerbond.log")
+		val resultsDirPath = "results-gen/cosimElevatorLoad"
+		val loader = new ModelStorage()
+		val src = loader.loadCandidates("instances/elevator_load_scenario.hintco")
+		val runner = new CosimRunner(new OutputProcessor(resultsDirPath), new FmuLoader)
+		val generator = new CandidatesGenerator(new ConstraintChecker,new VariantValidator, new VariantProcessor(runner))
+		generator.processAdaptations(src)
+		generator.createVariantTree(src)
+		generator.generateVariants(src, 1)
+		runner.close()
+	}
+	
 }

+ 51 - 0
HintCO/test/ua/ansymo/hintco/test/WvFormCosimRunnerTest.xtend

@@ -0,0 +1,51 @@
+package ua.ansymo.hintco.test
+
+import java.util.Map
+import org.junit.Test
+import ua.ansymo.hintco.CandidatesGenerator
+import ua.ansymo.hintco.ConstraintChecker
+import ua.ansymo.hintco.FmuLoader
+import ua.ansymo.hintco.IOutputProcessor
+import ua.ansymo.hintco.ModelStorage
+import ua.ansymo.hintco.OutputPortInstance
+import ua.ansymo.hintco.OutputProcessor
+import ua.ansymo.hintco.RootCandidateScenario
+import ua.ansymo.hintco.VariantProcessor
+import ua.ansymo.hintco.VariantValidator
+import ua.ansymo.hintco.WaveformCosimRunner
+
+import static org.junit.Assert.*
+import static ua.ansymo.hintco.ModelQuery.*
+import ua.ansymo.hintco.CosimRunner
+
+class WvFormCosimRunnerTest {
+	@Test
+	def void executeCosimulationWaterTankTest(){
+		val resultsDirPath = "results-gen/waveformWaterTankTest"
+		val loader = new ModelStorage()
+		val src = loader.loadCandidates("instances/waveform_watertanks.xmi")
+		
+		val Tank2WaterLevel = findIded("Tank2WaterLevel",src)
+		
+		var runner = new WaveformCosimRunner(new IOutputProcessor{
+			override initialize(RootCandidateScenario scenario, String variantID) {}
+			override terminate() {}
+			override setOutputs(int step, double time, Map<OutputPortInstance, Double> outVals) {
+				if (time > 4.0){
+					assertTrue(outVals.get(Tank2WaterLevel) > 0.5)
+				}
+			}
+		}, new FmuLoader)
+		runner = new WaveformCosimRunner(new OutputProcessor(resultsDirPath), new FmuLoader)
+		var generator = new CandidatesGenerator(new ConstraintChecker,new VariantValidator, new VariantProcessor(runner))
+		generator.createVariantTree(src)
+		generator.generateVariants(src, 1)
+		runner.close()
+		
+		val normalRunner = new CosimRunner(new OutputProcessor(resultsDirPath+"Normal"), new FmuLoader)
+		generator = new CandidatesGenerator(new ConstraintChecker,new VariantValidator, new VariantProcessor(normalRunner))
+		generator.createVariantTree(src)
+		generator.generateVariants(src, 1)
+		runner.close()
+	}
+}