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