Skip to content

Commit

Permalink
wip refactoring PlantSimulator
Browse files Browse the repository at this point in the history
  • Loading branch information
Steinar Elgsæter committed Dec 16, 2024
1 parent 695c709 commit 1e41e50
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 42 deletions.
113 changes: 93 additions & 20 deletions Dynamic/PlantSimulator/PlantSimulator.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Linq;
using System.Net.Http.Headers;
Expand Down Expand Up @@ -81,6 +82,9 @@ public Comment(string author, DateTime date, string comment, double plantScore =
/// </summary>
public class PlantSimulator
{

private const bool doDestBasedONYsimOfLastTimestep = false; //TODO: was false, but want to refactor so that "true" works.

/// <summary>
/// User-friendly name that may include white spaces.
/// </summary>
Expand Down Expand Up @@ -508,6 +512,8 @@ public bool SimulateSingle(TimeSeriesDataSet inputData, string singleModelName,
return SimulateSingle(inputData, singleModelName, false, out simData);
}



/// <summary>
/// Simulates a single model for a unit dataset and adds the output to unitData.Y_meas of the unitData, optionally with noise
/// </summary>
Expand Down Expand Up @@ -567,13 +573,15 @@ static private (bool, double[]) SimulateSingle(UnitDataSet unitData, ISimulatabl
inputData.Add(uName, unitData.U.GetColumn(colIdx));
uNames.Add(uName);
}
modelCopy.SetInputIDs(uNames.ToArray());
if (modelCopy.GetOutputID() == null)
modelCopy.SetOutputID("output");
modelCopy.SetInputIDs(uNames.ToArray());
if (modelCopy.GetOutputID() == null)
modelCopy.SetOutputID("output");

var sim = new PlantSimulator(new List<ISimulatableModel> { modelCopy });
var isOk = sim.SimulateSingle(inputData, singleModelName, false, out var simData);

// var isOk = PlantSimulator.SimulateSingle(inputData, modelCopy, out var simData);

PlantSimulator sim = new PlantSimulator(new List<ISimulatableModel> { modelCopy });
// var simData = new TimeSeriesDataSet();
var isOk = sim.SimulateSingle(inputData, singleModelName, false, out var simData);
if(!isOk)
return (false, null);
double[] y_sim = simData.GetValues(singleModelName, SignalType.Output_Y);
Expand Down Expand Up @@ -619,6 +627,7 @@ static private (bool, double[]) SimulateSingle(UnitDataSet unitData, ISimulatabl
public bool SimulateSingle(TimeSeriesDataSet inputData, string singleModelName,
bool doCalcYwithoutAdditiveTerms, out TimeSeriesDataSet simData)
{

if (!modelDict.ContainsKey(singleModelName))
{
simData = null;
Expand Down Expand Up @@ -664,9 +673,8 @@ public bool SimulateSingle(TimeSeriesDataSet inputData, string singleModelName,

// initalize
{
double[] inputVals = GetValuesFromEitherDataset(inputIDs, timeIdx, simData, inputData);
double[] outputVals =
GetValuesFromEitherDataset(new string[] { outputID }, timeIdx, simData, inputData);
double[] inputVals = GetValuesFromEitherDataset(model,inputIDs, timeIdx, simData, inputData);
double[] outputVals = GetValuesFromEitherDataset(model,new string[] { outputID }, timeIdx, simData, inputData);
simData.InitNewSignal(nameOfSimulatedSignal, outputVals[0], N.Value);
model.WarmStart(inputVals, outputVals[0]);
}
Expand Down Expand Up @@ -707,6 +715,7 @@ public bool SimulateSingle(TimeSeriesDataSet inputData, string singleModelName,
// disturbance estimation
if (modelDict[singleModelName].GetProcessModelType() == ModelType.SubProcess && doEstimateDisturbance)
{

// y_meas = y_internal+d as defined here
var y_meas = inputData.GetValues(outputID);
if (!(new Vec()).IsAllNaN(y_meas) && y_meas != null)
Expand All @@ -716,7 +725,26 @@ public bool SimulateSingle(TimeSeriesDataSet inputData, string singleModelName,
{
return false;
}
var est_disturbance = (new Vec()).Subtract(y_meas, y_sim);
// TODO: may need to "freeze" disturbance is there is a bad signal id?
// old: y_meas and y_sim are subtracted without time-shifting

double[] est_disturbance = null;
if (doDestBasedONYsimOfLastTimestep)
{
// note that actually
// y_meas[t] = y_proc[t-1] + D[t]
est_disturbance = new double[y_meas.Length];
for (int i = 1; i < y_meas.Length; i++)
{
est_disturbance[i] = y_meas[i]-y_sim[i-1];
}
est_disturbance[0] = est_disturbance[1];
}
else
{
est_disturbance = (new Vec()).Subtract(y_meas, y_sim);

}
simData.Add(SignalNamer.EstDisturbance(model), est_disturbance);
simData.Add(model.GetOutputID(), y_meas);
}
Expand Down Expand Up @@ -789,7 +817,7 @@ public bool Simulate (TimeSeriesDataSet inputData, out TimeSeriesDataSet simData
return false;
}

double[] inputVals = GetValuesFromEitherDataset(inputIDs, timeIdx, simData, inputDataMinimal);
double[] inputVals = GetValuesFromEitherDataset(model,inputIDs, timeIdx, simData, inputDataMinimal);

string outputID = model.GetOutputID();
if (outputID==null)
Expand All @@ -799,7 +827,7 @@ public bool Simulate (TimeSeriesDataSet inputData, out TimeSeriesDataSet simData
return false;
}
double[] outputVals =
GetValuesFromEitherDataset(new string[] { outputID }, timeIdx, simData, inputDataMinimal);
GetValuesFromEitherDataset(model,new string[] { outputID }, timeIdx, simData, inputDataMinimal);
if (outputVals != null)
{
model.WarmStart(inputVals, outputVals[0]);
Expand All @@ -824,14 +852,9 @@ public bool Simulate (TimeSeriesDataSet inputData, out TimeSeriesDataSet simData
{
var model = modelDict[orderedSimulatorIDs.ElementAt(modelIdx)];
string[] inputIDs = model.GetBothKindsOfInputIDs();
int inputDataLookBackIdx = 0;
if (model.GetProcessModelType() == ModelType.PID && timeIdx > 0)
{
inputDataLookBackIdx = 1;//if set to zero, model fails(requires changing model order).
}

double[] inputVals = GetValuesFromEitherDataset(inputIDs, lastGoodTimeIndex - inputDataLookBackIdx, simData, inputDataMinimal);

double[] inputVals = null;
inputVals = GetValuesFromEitherDataset(model, inputIDs, lastGoodTimeIndex, simData, inputDataMinimal);
if (inputVals == null)
{
Shared.GetParserObj().AddError("PlantSimulator.Simulate() failed. Model \"" + model.GetID() +
Expand Down Expand Up @@ -874,7 +897,7 @@ public bool Simulate (TimeSeriesDataSet inputData, out TimeSeriesDataSet simData
/// <param name="dataSet1"></param>
/// <param name="dataSet2"></param>
/// <returns></returns>
private double[] GetValuesFromEitherDataset(string[] inputIDs, int timeIndex,
private double[] GetValuesFromEitherDatasetInternal(string[] inputIDs, int timeIndex,
TimeSeriesDataSet dataSet1, TimeSeriesDataSet dataSet2)
{
double[] retVals = new double[inputIDs.Length];
Expand Down Expand Up @@ -905,6 +928,56 @@ private double[] GetValuesFromEitherDataset(string[] inputIDs, int timeIndex,
return retVals;
}

/// <summary>
/// Gets data for a given model from either of two datasets (usally the inputdata and possibly simulated data.
/// This method also has a special treatment of PID-inputs,
/// </summary>
/// <param name="model">the model that the data is for()</param>
/// <param name="inputIDs"></param>
/// <param name="timeIndex"></param>
/// <param name="dataSet1"></param>
/// <param name="dataSet2"></param>
/// <returns></returns>
public double[] GetValuesFromEitherDataset(ISimulatableModel model, string[] inputIDs, int timeIndex,
TimeSeriesDataSet dataSet1, TimeSeriesDataSet dataSet2)
{
if (model.GetProcessModelType() == ModelType.PID && timeIndex>0)
{

int lookBackIndex = 1;
double[] lookBackValues = GetValuesFromEitherDatasetInternal(inputIDs, timeIndex - lookBackIndex, dataSet1, dataSet2); ;
double[] currentValues = GetValuesFromEitherDatasetInternal(inputIDs, timeIndex, dataSet1, dataSet2);

// "use values from current data point when available, but fall back on using values from the previous sample if need be"
// for instance, always use the most current setpoint value, but if no disturbance vector is given, then use the y_proc simulated from the last iteration.
double[] retValues = new double[currentValues.Length];
retValues = lookBackValues;

// adding in the below code seems to remove the issue with there being a one sample wait time before the effect of a setpoint
// is seen on the output, but causes there to be small deviation between what the PlantSimulator.SimulateSingle and PlantSimulator.Simulate
// seem to return for a PID-loop in the test BasicPID_CompareSimulateAndSimulateSingle_MustGiveSameResultForDisturbanceEstToWork
/*
for (int i = 0; i < currentValues.Length; i++)
{
if (Double.IsNaN(currentValues[i]))
{
retValues[i] = lookBackValues[i];
}
else
{
retValues[i] = currentValues[i];
}
}*/
return retValues;
}
else
{
return GetValuesFromEitherDatasetInternal(inputIDs, timeIndex, dataSet1, dataSet2);
}
}



/// <summary>
/// Creates a JSON text string serialization of this object.
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions Dynamic/PlantSimulator/PlantSimulatorInitalizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ private bool EstimateDisturbances(ref TimeSeriesDataSet inputData, ref TimeSerie
if (upstreamModels.Count == 0)
continue;
var processId = upstreamModels.First();

var isOK = simulator.SimulateSingleInternal(inputData, processId,
out TimeSeriesDataSet singleSimDataSetWithDisturbance);
if (isOK)
Expand Down
8 changes: 7 additions & 1 deletion Dynamic/SimulatableModels/PIDModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,13 @@ public ISimulatableModel Clone(string ID = null)
IDinternal = ID;

var newPidParameters = new PidParameters(pidParameters);
return new PidModel(newPidParameters, IDinternal);

var clone = new PidModel(newPidParameters, IDinternal);

clone.ModelInputIDs = GetModelInputIDs();
clone.outputID = GetOutputID();

return clone;
}


Expand Down
7 changes: 6 additions & 1 deletion Dynamic/SimulatableModels/UnitModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -818,7 +818,12 @@ public ISimulatableModel Clone(string IDexternal = null)
IDinternal = IDexternal;

var newModelParams = modelParameters.CreateCopy();
return new UnitModel(newModelParams, IDinternal);
var retModel = new UnitModel(newModelParams, IDinternal);
retModel.additiveInputIDs = additiveInputIDs;
retModel.ModelInputIDs = ModelInputIDs;
retModel.outputID = outputID;
retModel.outputIdentID = outputIdentID;
return retModel;
}


Expand Down
38 changes: 26 additions & 12 deletions TimeSeriesAnalysis.Tests/Tests/DisturbanceEstimatorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,13 @@ public void CommonPlotAndAsserts(UnitDataSet pidDataSet, double[] d_est, double[
Replace(")", "_").Replace(",", "_") + "y";
if (false)
{
Shared.EnablePlots();
Plot.FromList(new List<double[]>{ pidDataSet.Y_meas, pidDataSet.Y_setpoint,
pidDataSet.U.GetColumn(0),
d_est, trueDisturbance },
pidDataSet.U.GetColumn(0),
d_est, trueDisturbance },
new List<string> { "y1=y meas", "y1=y set", "y2=u(right)", "y3=est disturbance", "y3=true disturbance" },
pidDataSet.GetTimeBase(), caseId);
Shared.DisablePlots();
}
Assert.IsTrue(vec.Mean(vec.Abs(vec.Subtract(trueDisturbance, d_est))) < distTrueAmplitude / 10,"true disturbance and actual disturbance too far apart");
}
Expand Down Expand Up @@ -105,8 +107,16 @@ public void PlantSimulatorSingle_StepDisturbance_EstimatesOk(double stepAmplitud
[TestCase(4)]
public void PlantSimulator_StepDisturbance_EstimatesOk(double stepAmplitude)
{
var locModelParameters = new UnitParameters
{
TimeConstant_s = 15,
LinearGains = new double[] { 1 },
TimeDelay_s = 0,
Bias = 5
};

var trueDisturbance = TimeSeriesCreator.Step(100, N, 0, stepAmplitude);
DisturbanceTestUsingPlantSimulator(new UnitModel(dynamicModelParameters, "PlantSim_d"), trueDisturbance);
DisturbanceTestUsingPlantSimulator(locModelParameters, trueDisturbance);
}

[TestCase(4,1)]
Expand All @@ -116,7 +126,7 @@ public void PlantSimulator_StepDisturbanceANDSetPointStep_EstimatesOk(double dis
// Shared.EnablePlots();
var trueDisturbance = TimeSeriesCreator.Step(100, N, 0, disturbanceStepAmplitude);
var setpoint = TimeSeriesCreator.Step(50, N, 50, 50+setpointAmplitude);
DisturbanceTestUsingPlantSimulator(new UnitModel(dynamicModelParameters, "PlantSim_d"), trueDisturbance, true, setpoint);
DisturbanceTestUsingPlantSimulator(dynamicModelParameters, trueDisturbance, true, setpoint);
//Shared.DisablePlots();
}

Expand Down Expand Up @@ -149,15 +159,15 @@ public void GenericDisturbanceTest (UnitModel processModel, double[] trueDistur
}


public void DisturbanceTestUsingPlantSimulator(UnitModel processModel, double[] trueDisturbance,
public void DisturbanceTestUsingPlantSimulator(UnitParameters unitParams, double[] trueDisturbance,
bool doAssertResult = true, double[] externalYset=null)
{
TimeSeriesDataSet referenceSimDataSet;
TimeSeriesDataSet referenceInputDataSet;
// 1 .create synthetic dataset - where a "true" known disturbance is specified
{
var pidModel1 = new PidModel(pidParameters1, "PID1");
var processModel2 = new UnitModel(dynamicModelParameters, "Proc1");
var processModel2 = new UnitModel(unitParams, "Proc1");
var plantSim = new PlantSimulator(
new List<ISimulatableModel> { pidModel1, processModel2 });
plantSim.ConnectModels(processModel2, pidModel1);
Expand All @@ -182,7 +192,7 @@ public void DisturbanceTestUsingPlantSimulator(UnitModel processModel, double[]
{
var pidModel1 = new PidModel(pidParameters1, "PID1");

var processModel3 = new UnitModel(dynamicModelParameters, "Proc1");
var processModel3 = new UnitModel(unitParams, "Proc1");

var distSignal = SignalNamer.EstDisturbance(processModel3);
processModel3.AddSignalToOutput(distSignal);
Expand All @@ -203,28 +213,29 @@ public void DisturbanceTestUsingPlantSimulator(UnitModel processModel, double[]

/////////////////
///
///adding u and y to inputdata, should enable the plant simualtor to back-calculate the disturbance.
///adding u and y to inputdata, should enable the plant simulator to back-calculate the disturbance.
///
var inputData = new TimeSeriesDataSet();
inputData.Add(ysetSignal, referenceInputDataSet.GetValues("yset"));
inputData.Add(uMeasSignal, referenceSimDataSet.GetValues("PID1", SignalType.PID_U));
inputData.Add(ymeasSignal, referenceSimDataSet.GetValues("Proc1", SignalType.Output_Y));
/////////////////
Assert.IsTrue(inputData.GetSignalNames().Count() == 3 );//sanity check for configuration errors
Assert.IsTrue(inputData.GetSignalNames().Count() == 3,"configuration errors" );//sanity check for configuration errors
inputData.CreateTimestamps(timeBase_s);

//////////////////////////////////
var isOK = plantSim.Simulate(inputData, out var simDataSetWithDisturbance);
//////////////////////////////////
Assert.IsTrue(isOK);
Assert.IsTrue(plantSim.PlantFitScore == 100);
Assert.IsTrue(simDataSetWithDisturbance.ContainsSignal(distSignal));

if (doAssertResult)
{
var pidDataSet = plantSim.GetUnitDataSetForPID(inputData.Combine(simDataSetWithDisturbance), pidModel1);
CommonPlotAndAsserts(pidDataSet, simDataSetWithDisturbance.GetValues(distSignal),
trueDisturbance);
}
Assert.IsTrue(isOK, "simulate dataset with the disturbane FAILED!!");
Assert.IsTrue(plantSim.PlantFitScore == 100, "expect plant fit score 100");
Assert.IsTrue(simDataSetWithDisturbance.ContainsSignal(distSignal), "simulated dataset does not contain the disturbance signal");
}
}

Expand Down Expand Up @@ -271,6 +282,9 @@ public void DisturbanceTestUsingPlantSimulateSingle(UnitModel processModel, doub
inputData.CreateTimestamps(timeBase_s);
var isOK = plantSim.SimulateSingle(inputData, processModel.ID,
out TimeSeriesDataSet simDataSetWithDisturbance);
// var isOK = PlantSimulator.SimulateSingle(inputData, processModel,
// out TimeSeriesDataSet simDataSetWithDisturbance);

Assert.IsTrue(isOK);
Assert.IsTrue(simDataSetWithDisturbance.ContainsSignal(SignalNamer.EstDisturbance(processModel)));
Assert.IsTrue(simDataSetWithDisturbance.ContainsSignal(processModel.GetID()),
Expand Down
8 changes: 4 additions & 4 deletions TimeSeriesAnalysis.Tests/Tests/PidIdentUnitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ public void SetUp()


// Tendency of Kp and Ti to be biased lower when there is noise in Y
[TestCase(1, 0.0,1)]
[TestCase(1, 0.1,8)]
[TestCase(2, 0.1,5)]
[TestCase(5, 0.1,2)]
[TestCase(1, 0.0,10)]
[TestCase(1, 0.1,10)]
[TestCase(2, 0.1,10)]
[TestCase(5, 0.1,10)]

public void SetpointStep_WNoise_KpAndTiEstimatedOk(double ySetAmplitude, double yNoiseAmplitude, double tolerancePrc)
{
Expand Down
Loading

0 comments on commit 1e41e50

Please sign in to comment.