diff --git a/Dynamic/PlantSimulator/PlantSimulator.cs b/Dynamic/PlantSimulator/PlantSimulator.cs
index 1aa229f..c62eebf 100644
--- a/Dynamic/PlantSimulator/PlantSimulator.cs
+++ b/Dynamic/PlantSimulator/PlantSimulator.cs
@@ -595,6 +595,21 @@ public bool SimulateSingle(TimeSeriesDataSet inputData, string singleModelName,
return SimulateSingleInternalCore(inputData, singleModelName, false, out simData);
}
+ ///
+ /// Simulate a single model to get the output including any additive inputs.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static bool SimulateSingle(TimeSeriesDataSet inputData, ISimulatableModel model, out TimeSeriesDataSet simData)
+ {
+ var plant = new PlantSimulator(new List { model });
+
+ return plant.Simulate(inputData, out simData );
+ }
+
+
///
/// Simulates a single model for a unit dataset and adds the output to unitData.Y_meas of the unitData, optionally with noise
///
diff --git a/TimeSeriesAnalysis.Tests/Tests/PlantSimulatorSISOTests.cs b/TimeSeriesAnalysis.Tests/Tests/PlantSimulatorSISOTests.cs
index 70996ad..f30f55b 100644
--- a/TimeSeriesAnalysis.Tests/Tests/PlantSimulatorSISOTests.cs
+++ b/TimeSeriesAnalysis.Tests/Tests/PlantSimulatorSISOTests.cs
@@ -478,6 +478,7 @@ public void BasicPID_SetpointStep_RunsAndConverges(bool delayPidOutputOneSample)
[TestCase]
public void BasicPID_CompareSimulateAndSimulateSingle_MustGiveSameResultForDisturbanceEstToWork()
{
+ //var pidCopy = pidModel1.Clone();
double newSetpoint = 51;
int N = 100;
var plantSim = new PlantSimulator(
@@ -492,13 +493,17 @@ public void BasicPID_CompareSimulateAndSimulateSingle_MustGiveSameResultForDistu
var newSet = new TimeSeriesDataSet();
newSet.AddSet(inputData);
- // var outputSignalName = SignalNamer.GetSignalName(processModel1.GetID(), SignalType.Output_Y);
- // newSet.Add(outputSignalName, simData.GetValues(outputSignalName));
newSet.AddSet(simData);
newSet.SetTimeStamps(inputData.GetTimeStamps().ToList());
- // var isOk2 = PlantSimulator.SimulateSingle(newSet, pidModel1, out var simData2);
- var isOK2 = plantSim.SimulateSingle(newSet, pidModel1.ID,out TimeSeriesDataSet simData2);
+ // slight deviation between SimulateSingle and Simulate regardless of which version is used:
+ //v1
+ // var isOK2 = plantSim.SimulateSingle(newSet, pidModel1.ID,out TimeSeriesDataSet simData2);
+ //v2
+ // pidCopy.SetInputIDs(pidModel1.GetModelInputIDs());
+ // var isOk2 = PlantSimulator.SimulateSingle(newSet, pidCopy, out var simData2);
+ //v3
+ var isOk2 = PlantSimulator.SimulateSingle(newSet, pidModel1, out var simData2);
if (true)
{
diff --git a/docs/articles/processsimulator.md b/docs/articles/processsimulator.md
index 733ee79..c077f12 100644
--- a/docs/articles/processsimulator.md
+++ b/docs/articles/processsimulator.md
@@ -55,13 +55,36 @@ Each model has a number of inputs that travel through the model, each with an id
In addition, models support adding signals directly to the output. This is a feature intended for modeling disturbances.
The IDs of such signals are referred to as *additive input IDs*.
-### Estimating disturbances and simulating their effects using PlantSimulator.SimulateSingle()
+### Estimating disturbances and simulating their effects
If the ``inputData`` given to the PlantSimulator includes measured pid-outputs ``u`` and process outputs ``y`` of feedback loops,
then PlantSimulator uses that
-``y_meas = y_proc+d``
+``y_meas[k] = y_proc[k-1]+d[k]``
calculate the disturbance vector ``d`` and this disturbance is then simulated as the ``driving force`` of closed loop dynamics.
Thus when the process model is known and inputs to the model are given, the method ``PlantSimulator.SimulateSingle()`` is used to
simulate the disturbance vector as part of the initialization of ``PlantSimulator.Simulate()``
+### Closed loops : simulation order and disturbance
+
+In a closed loop the simulation order will be
+
+- *PidModel* reads ``y_meas[k]`` and ``y_setpoint[k]`` and calculates ``u_pid[k]``
+- the process model (usally a *UnitModel*) gives ``u_pid[k]`` and any other inputs ``u[k]`` and outputs an internal state ``y_proc[k]``
+- In the next iteration y_meas[k+1] = y_proc[k] + d[k+1]
+
+This is by convention, and is how the simulator avoids both reading to ymeas[k] and writing to ymeas[k] in the same iteration.
+
+This above means that implicitly all closed-loop processes in a manner of speaking have a time delay of 1.
+
+It is somewhat challenging to define this behavior so that it is consistent when a model is for instance open-loop tested, then a pid-control is applied.
+A static process by definition has ``y[k] = f(u[k])`` but in a closed loop that could mean that the measurement y[k] that is used to determine u_pid[k]
+is again overwritten by the output of astatic process.
+
+It coudl also be possible to deal with this by stating that PID-controllers by convention deal with the signals of the previous step, but so that
+u_pid[k] = f(y_set[k], y_meas[k-1]), but this does not match real-world industrial data, it introduces a lag. It may be that in some cases pid-controllers
+are implemented like this, but often the analysis is done on down-sampled data, and in that case ``u_pid[k]`` appears simultanous to changes in ``y_meas[k]``
+
+**Implicitly the above also defines how to interpret the disturbance d[k].** To be extremely precise with how this is defined is important, as the PlantSimulator is
+used internally to back-calculte disturbances as is described in the above section, and how the distrubance is calcualted will again be important as both single simulations and co-simulations
+are used by ClosedLoopUnitIdentifier to identify the process model including possibly time constants and time-delays.
\ No newline at end of file