From b8ad3796ad8482901c6ea2081cc5c1f82b074df8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steinar=20Elgs=C3=A6ter?= Date: Tue, 26 Nov 2024 14:34:06 +0100 Subject: [PATCH] - fix an issue with SetOperatingPoint for GainSchedModelParameters - add a flag to GainSchedFittingSpecs to determine how to set operating point, - fix an issue when attempting to set the mean of the dataset as the operating point. --- Dynamic/Identification/FitScoreCalculator.cs | 20 +- .../Identification/GainSchedFittingSpecs.cs | 9 + Dynamic/Identification/GainSchedIdentifier.cs | 32 +- Dynamic/PlantSimulator/PlantSimulator.cs | 396 ++++++++++-------- .../SimulatableModels/GainSchedParameters.cs | 2 +- .../Tests/DisturbanceEstimatorTests.cs | 5 +- .../Tests/GainSchedIdentifyTests.cs | 5 +- TimeSeriesAnalysis.csproj | 2 +- 8 files changed, 264 insertions(+), 207 deletions(-) diff --git a/Dynamic/Identification/FitScoreCalculator.cs b/Dynamic/Identification/FitScoreCalculator.cs index c537fa1..15c1d77 100644 --- a/Dynamic/Identification/FitScoreCalculator.cs +++ b/Dynamic/Identification/FitScoreCalculator.cs @@ -55,7 +55,7 @@ public static double Calc(double[] meas, double[] sim) static public double GetPlantWideSimulated(PlantSimulator plantSimObj, TimeSeriesDataSet inputData, TimeSeriesDataSet simData) { - const string disturbanceSignalPrefix = "_D"; + // const string disturbanceSignalPrefix = "_D"; List fitScores = new List(); foreach (var modelName in plantSimObj.modelDict.Keys) { @@ -68,13 +68,17 @@ static public double GetPlantWideSimulated(PlantSimulator plantSimObj, TimeSerie if (outputName == "" || outputName == null) continue; + + // + // Step 1: get the output signals of individual models in the measured dataset + // + if (plantSimObj.modelDict[modelName].GetProcessModelType() == ModelType.PID) { if (inputData.ContainsSignal(outputName)) { measY = inputData.GetValues(outputName); } - } else //if (plantSimObj.modelDict[modelName].GetProcessModelType() == ModelType.SubProcess) { @@ -84,25 +88,29 @@ static public double GetPlantWideSimulated(PlantSimulator plantSimObj, TimeSerie { measY = inputData.GetValues(outputIdentName); } - else if (simData.ContainsSignal(outputIdentName)) + /* else if (simData.ContainsSignal(outputIdentName)) { measY = simData.GetValues(outputIdentName); - } + }*/ } // add in fit of process output, but only if "additive output signal" is not a // locally identified "_D_" signal, as then output matches 100% always (any model error is put into disturbance signal as well) - else if (modelObj.GetAdditiveInputIDs() != null) + /* else if (modelObj.GetAdditiveInputIDs() != null) { if (!modelObj.GetAdditiveInputIDs()[0].StartsWith(disturbanceSignalPrefix)) { measY = inputData.GetValues(outputName); } - } + }*/ else if (inputData.ContainsSignal(outputName)) { measY = inputData.GetValues(outputName); } } + + // + // Step 2: get the corresponding signal from the simulated dataset + // if (simData.ContainsSignal(outputName)) { simY = simData.GetValues(outputName); diff --git a/Dynamic/Identification/GainSchedFittingSpecs.cs b/Dynamic/Identification/GainSchedFittingSpecs.cs index 2f12cbb..c65f12c 100644 --- a/Dynamic/Identification/GainSchedFittingSpecs.cs +++ b/Dynamic/Identification/GainSchedFittingSpecs.cs @@ -29,5 +29,14 @@ public GainSchedFittingSpecs() /// Default is 0, i.e. first input is used for gain-scheduling. /// public int uGainScheduledInputIndex = 0; + + /// + /// If set to false, the model starts in the same value as the tuning set starts in, + /// if set to true, the model passes through the mean of the dataset. + /// + public bool DoSetOperatingPointToDatasetMean = false; + + + } } diff --git a/Dynamic/Identification/GainSchedIdentifier.cs b/Dynamic/Identification/GainSchedIdentifier.cs index 4e56507..98c6f32 100644 --- a/Dynamic/Identification/GainSchedIdentifier.cs +++ b/Dynamic/Identification/GainSchedIdentifier.cs @@ -329,7 +329,7 @@ public static GainSchedModel IdentifyForGivenThresholds(UnitDataSet dataSet, Gai if (idParams.Fitting.WasAbleToIdentify) { // simulate the model and determine the optimal bias term: - DetermineOperatingPointAndSimulate(ref idParams, ref dataSet); + DetermineOperatingPointAndSimulate(ref idParams, ref dataSet, gsFittingSpecs.DoSetOperatingPointToDatasetMean); if (doTimeDelayEstimation) EstimateTimeDelay(ref idParams, ref dataSet); @@ -493,12 +493,12 @@ static private (GainSchedParameters, int) ChooseBestModelFromSimulationList(List /// /// the gain-scheduled parameters that are to be updated with an operating point. /// tuning dataset to be updated with Y_sim + /// set the operating point to be the centre of the tuning set, if set to false models is set to match the start of the dataset + /// /// true if able to estiamte bias, otherwise false - private static bool DetermineOperatingPointAndSimulate(ref GainSchedParameters gsParams, ref UnitDataSet dataSet) + private static bool DetermineOperatingPointAndSimulate(ref GainSchedParameters gsParams, ref UnitDataSet dataSet, bool doMeanU) { // if set to true, then the operating point is set to the average U in the dataset, otherwise it is set to equal the start - const bool doMeanU = false; // can be true or false, makes little difference? - var gsIdentModel = new GainSchedModel(gsParams, "ident_model"); var vec = new Vec(dataSet.BadDataID); @@ -508,7 +508,7 @@ private static bool DetermineOperatingPointAndSimulate(ref GainSchedParameters g var val = vec.Mean(vec.GetValues(dataSet.U.GetColumn(gsParams.GainSchedParameterIndex), dataSet.IndicesToIgnore)); if (val.HasValue && doMeanU) { - desiredOpU = dataSet.U.GetColumn(gsParams.GainSchedParameterIndex).First(); + desiredOpU = val.Value; //dataSet.U.GetColumn(gsParams.GainSchedParameterIndex).First(); } gsParams.MoveOperatingPointUWithoutChangingModel(desiredOpU); @@ -768,7 +768,7 @@ private static GainSchedParameters EvaluateMultipleTimeConstantsForGivenGainThre { curGainSchedParams_separateTc.TimeConstant_s = curTimeConstants; curGainSchedParams_separateTc.TimeConstantThresholds = new double[] { candidateTcThresholds[i] }; - DetermineOperatingPointAndSimulate(ref curGainSchedParams_separateTc, ref DS_separateTc); + DetermineOperatingPointAndSimulate(ref curGainSchedParams_separateTc, ref DS_separateTc,false); candModelsStep2.Add(new GainSchedParameters(curGainSchedParams_separateTc)); // candYsimStep2.Add((double[])DS_separateTc.Y_sim.Clone()); } @@ -776,27 +776,9 @@ private static GainSchedParameters EvaluateMultipleTimeConstantsForGivenGainThre { curGainSchedParams_separateTc.TimeConstant_s = new double[] { curTimeConstants.First() }; curGainSchedParams_separateTc.TimeConstantThresholds = null; - DetermineOperatingPointAndSimulate(ref curGainSchedParams_separateTc, ref DS_separateTc); + DetermineOperatingPointAndSimulate(ref curGainSchedParams_separateTc, ref DS_separateTc,false); candModelsStep2.Add(new GainSchedParameters(curGainSchedParams_separateTc)); } -/* - { - var curGainSchedParams_commonTc1 = new GainSchedParameters(curGainSchedParams_separateTc); - curGainSchedParams_commonTc1.TimeConstant_s = new double[] { curTimeConstants[0] }; - curGainSchedParams_commonTc1.TimeConstantThresholds = null; - DetermineOperatingPointAndSimulate(ref curGainSchedParams_commonTc1, ref DS_commonTc1); - candModelsStep2.Add(new GainSchedParameters(curGainSchedParams_commonTc1)); - // candYsimStep2.Add((double[])DS_commonTc1.Y_sim.Clone()); - } - { - var curGainSchedParams_commonTc2 = new GainSchedParameters(curGainSchedParams_separateTc); - curGainSchedParams_commonTc2.TimeConstant_s = new double[] { curTimeConstants[1] }; - curGainSchedParams_commonTc2.TimeConstantThresholds = null; - DetermineOperatingPointAndSimulate(ref curGainSchedParams_commonTc2, ref DS_commonTc2); - candModelsStep2.Add(new GainSchedParameters(curGainSchedParams_commonTc2)); - // candYsimStep2.Add((double[])DS_commonTc2.Y_sim.Clone()); - } -*/ bool doDebugPlot = false; if (doDebugPlot) diff --git a/Dynamic/PlantSimulator/PlantSimulator.cs b/Dynamic/PlantSimulator/PlantSimulator.cs index 49662e9..43f3bb4 100644 --- a/Dynamic/PlantSimulator/PlantSimulator.cs +++ b/Dynamic/PlantSimulator/PlantSimulator.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.Linq; using System.Net.Http.Headers; using System.Reflection; @@ -117,92 +118,7 @@ public class PlantSimulator /// /// The fitScore of the plant the last time it was saved. /// - public double PlantFitScore; - - /// - /// Returns a unit data set for a given unitModel. - /// - /// - /// - /// - public UnitDataSet GetUnitDataSetForProcess(TimeSeriesDataSet inputData, UnitModel unitModel) - { - UnitDataSet dataset = new UnitDataSet(); - dataset.U = new double[inputData.GetLength().Value, 1]; - - dataset.Times = inputData.GetTimeStamps(); - var inputIDs = unitModel.GetModelInputIDs(); - var outputID = unitModel.GetOutputID(); - dataset.Y_meas = inputData.GetValues(outputID); - for (int inputIDidx = 0; inputIDidx < inputIDs.Length; inputIDidx++) - { - var inputID = inputIDs[inputIDidx]; - var curCol = inputData.GetValues(inputID); - dataset.U.WriteColumn(inputIDidx, curCol); - } - return dataset; - } - - - /// - /// Returns a "unitDataSet" for the given pidModel in the plant. - /// This function only works when the unit model connected to the pidModel only has a single input. - /// - /// - /// - /// - public UnitDataSet GetUnitDataSetForPID(TimeSeriesDataSet inputData,PidModel pidModel) - { - var unitModID = connections.GetUnitModelControlledByPID(pidModel.GetID(),modelDict); - string[] modelInputIDs = null; - if (unitModID != null) - { - modelInputIDs = modelDict[unitModID].GetModelInputIDs(); - } - UnitDataSet dataset = new UnitDataSet(); - - if (modelInputIDs != null) - { - dataset.U = new double[inputData.GetLength().Value, modelInputIDs.Length]; - for (int modelInputIdx = 0; modelInputIdx < modelInputIDs.Length; modelInputIdx++) - { - var inputID = modelInputIDs[modelInputIdx]; - dataset.U.WriteColumn(modelInputIdx, inputData.GetValues(inputID)); - } - } - else - { - dataset.U = new double[inputData.GetLength().Value, 1]; - dataset.U.WriteColumn(0, inputData.GetValues(pidModel.GetOutputID())); - } - - dataset.Times = inputData.GetTimeStamps(); - var inputIDs = pidModel.GetModelInputIDs(); - - for (int inputIDidx=0; inputIDidx /// Constructor @@ -210,7 +126,7 @@ public UnitDataSet GetUnitDataSetForPID(TimeSeriesDataSet inputData,PidModel pid /// A list of process models, each implementing ISimulatableModel /// optional name of plant, used when serializing /// optional description of plant - public PlantSimulator(List processModelList, string plantName="", string plantDescription="") + public PlantSimulator(List processModelList, string plantName = "", string plantDescription = "") { externalInputSignalIDs = new List(); this.comments = new List(); @@ -244,7 +160,7 @@ public PlantSimulator(List processModelList, string plantName /// /// /// returns signalID or null if something went wrong - public string AddAndConnectExternalSignal(ISimulatableModel model,string signalID, SignalType type, int index = 0) + public string AddAndConnectExternalSignal(ISimulatableModel model, string signalID, SignalType type, int index = 0) { ModelType modelType = model.GetProcessModelType(); externalInputSignalIDs.Add(signalID); @@ -290,7 +206,7 @@ public string AddAndConnectExternalSignal(ISimulatableModel model,string signalI } else { - Shared.GetParserObj().AddError("PlantSimulator.AddSignal was unable to add signal '"+ signalID+"'" ); + Shared.GetParserObj().AddError("PlantSimulator.AddSignal was unable to add signal '" + signalID + "'"); return null; } @@ -308,7 +224,7 @@ public string AddAndConnectExternalSignal(ISimulatableModel model,string signalI public string AddExternalSignal(ISimulatableModel model, SignalType type, int index = 0) { string signalID = SignalNamer.GetSignalName(model.GetID(), type, index); - return AddAndConnectExternalSignal(model,signalID,type,index); + return AddAndConnectExternalSignal(model, signalID, type, index); } /// @@ -330,7 +246,7 @@ public bool ConnectSignalToInput(string signalID, ISimulatableModel model, int i /// /// /// - public bool ConnectModelToOutput(ISimulatableModel disturbanceModel, ISimulatableModel model ) + public bool ConnectModelToOutput(ISimulatableModel disturbanceModel, ISimulatableModel model) { model.AddSignalToOutput(disturbanceModel.GetOutputID()); return true; @@ -343,14 +259,14 @@ public bool ConnectModelToOutput(ISimulatableModel disturbanceModel, ISimulatabl /// the downstream model, meaning the model whose input will be connected /// input index of the downstream model to connect to (default is first input) /// returns the signal id if all is ok, otherwise null. - public string ConnectModels(ISimulatableModel upstreamModel, ISimulatableModel downstreamModel, int? inputIndex=null) + public string ConnectModels(ISimulatableModel upstreamModel, ISimulatableModel downstreamModel, int? inputIndex = null) { ModelType upstreamType = upstreamModel.GetProcessModelType(); ModelType downstreamType = downstreamModel.GetProcessModelType(); string outputId = upstreamModel.GetOutputID(); int nInputs = downstreamModel.GetLengthOfInputVector(); - if (nInputs == 1 && inputIndex ==0) + if (nInputs == 1 && inputIndex == 0) { downstreamModel.SetInputIDs(new string[] { outputId }); } @@ -383,7 +299,7 @@ public string ConnectModels(ISimulatableModel upstreamModel, ISimulatableModel d return false; } }*/ - else + else { var isOk = downstreamModel.SetInputIDs(new string[] { outputId }, inputIndex); if (!isOk) @@ -396,6 +312,15 @@ public string ConnectModels(ISimulatableModel upstreamModel, ISimulatableModel d return outputId; } + /// + /// Get ConnenectionParser object + /// + /// + public ConnectionParser GetConnections() + { + return connections; + } + /// /// Get a TimeSeriesDataSet of all external signals of model /// @@ -405,25 +330,142 @@ public string[] GetExternalSignalIDs() return externalInputSignalIDs.ToArray(); } + /// - /// Get ConnenectionParser object + /// Get dictionary of all models /// /// - public ConnectionParser GetConnections() + public Dictionary GetModels() { - return connections; + return modelDict; } + /// - /// Get dictionary of all models + /// Returns a unit data set for a given unitModel. /// + /// + /// /// - public Dictionary GetModels() + public UnitDataSet GetUnitDataSetForProcess(TimeSeriesDataSet inputData, UnitModel unitModel) { - return modelDict; + UnitDataSet dataset = new UnitDataSet(); + dataset.U = new double[inputData.GetLength().Value, 1]; + + dataset.Times = inputData.GetTimeStamps(); + var inputIDs = unitModel.GetModelInputIDs(); + var outputID = unitModel.GetOutputID(); + dataset.Y_meas = inputData.GetValues(outputID); + for (int inputIDidx = 0; inputIDidx < inputIDs.Length; inputIDidx++) + { + var inputID = inputIDs[inputIDidx]; + var curCol = inputData.GetValues(inputID); + dataset.U.WriteColumn(inputIDidx, curCol); + } + return dataset; } + /// + /// Returns a "unitDataSet" for the given pidModel in the plant. + /// This function only works when the unit model connected to the pidModel only has a single input. + /// + /// + /// + /// + public UnitDataSet GetUnitDataSetForPID(TimeSeriesDataSet inputData, PidModel pidModel) + { + var unitModID = connections.GetUnitModelControlledByPID(pidModel.GetID(), modelDict); + string[] modelInputIDs = null; + if (unitModID != null) + { + modelInputIDs = modelDict[unitModID].GetModelInputIDs(); + } + UnitDataSet dataset = new UnitDataSet(); + + if (modelInputIDs != null) + { + dataset.U = new double[inputData.GetLength().Value, modelInputIDs.Length]; + for (int modelInputIdx = 0; modelInputIdx < modelInputIDs.Length; modelInputIdx++) + { + var inputID = modelInputIDs[modelInputIdx]; + dataset.U.WriteColumn(modelInputIdx, inputData.GetValues(inputID)); + } + } + else + { + dataset.U = new double[inputData.GetLength().Value, 1]; + dataset.U.WriteColumn(0, inputData.GetValues(pidModel.GetOutputID())); + } + + dataset.Times = inputData.GetTimeStamps(); + var inputIDs = pidModel.GetModelInputIDs(); + + for (int inputIDidx = 0; inputIDidx < inputIDs.Length; inputIDidx++) + { + var inputID = inputIDs[inputIDidx]; + + if (inputIDidx == (int)PidModelInputsIdx.Y_setpoint) + { + dataset.Y_setpoint = inputData.GetValues(inputID); + } + else if (inputIDidx == (int)PidModelInputsIdx.Y_meas) + { + dataset.Y_meas = inputData.GetValues(inputID); + } + //todo: feedforward? + /*else if (type == SignalType.Output_Y_sim) + { + dataset.U.WriteColumn(1, inputData.GetValues(inputID)); + } + else + { + throw new Exception("unexepcted signal type"); + }*/ + } + return dataset; + } + + + + /// + /// + /// + /// + /// + /// + /// + /// + private double[] GetValuesFromEitherDataset(string[] inputIDs, int timeIndex, + TimeSeriesDataSet dataSet1, TimeSeriesDataSet dataSet2) + { + double[] retVals = new double[inputIDs.Length]; + + int index = 0; + foreach (var inputId in inputIDs) + { + double? retVal = null; + if (dataSet1.ContainsSignal(inputId)) + { + retVal = dataSet1.GetValue(inputId, timeIndex); + } + else if (dataSet2.ContainsSignal(inputId)) + { + retVal = dataSet2.GetValue(inputId, timeIndex); + } + if (!retVal.HasValue) + { + retVals[index] = Double.NaN; + } + else + { + retVals[index] = retVal.Value; + } + + index++; + } + return retVals; + } /// @@ -435,7 +477,7 @@ public Dictionary GetModels() /// public bool SimulateSingleInternal(TimeSeriesDataSet inputData, string singleModelName, out TimeSeriesDataSet simData) { - return SimulateSingle(inputData,singleModelName,true, out simData); + return SimulateSingle(inputData, singleModelName, true, out simData); } /// @@ -459,7 +501,7 @@ public bool SimulateSingle(TimeSeriesDataSet inputData, string singleModelName, /// a seed value of the randm noise(specify so that tests are repeatable) /// public static (bool, double[]) SimulateSingleToYmeas(UnitDataSet unitData, ISimulatableModel model, double noiseAmplitude = 0, - int noiseSeed= 123) + int noiseSeed = 123) { return SimulateSingle(unitData, model, true, noiseAmplitude, true, noiseSeed); } @@ -471,7 +513,7 @@ public static (bool, double[]) SimulateSingleToYmeas(UnitDataSet unitData, ISimu /// /// /// - public static (bool, double[]) SimulateSingle(UnitDataSet unitData, ISimulatableModel model, bool addSimToUnitData) + public static (bool, double[]) SimulateSingle(UnitDataSet unitData, ISimulatableModel model, bool addSimToUnitData) { return SimulateSingle(unitData, model, false, 0, addSimToUnitData, 0); @@ -487,9 +529,9 @@ public static (bool, double[]) SimulateSingle(UnitDataSet unitData, ISimulatabl /// if true, the Y_sim of unitData has the simulation result written two i /// the seed value of the noise to be added /// a tuple, first aa true if able to simulate, otherwise false, second is the simulated time-series - static private (bool, double[]) SimulateSingle(UnitDataSet unitData, ISimulatableModel model,bool writeToYmeas= false, - double noiseAmplitude=0, - bool addSimToUnitData=false, int seedNr=123) + static private (bool, double[]) SimulateSingle(UnitDataSet unitData, ISimulatableModel model, bool writeToYmeas = false, + double noiseAmplitude = 0, + bool addSimToUnitData = false, int seedNr = 123) { var inputData = new TimeSeriesDataSet(); var singleModelName = "SimulateSingle"; @@ -503,7 +545,7 @@ static private (bool, double[]) SimulateSingle(UnitDataSet unitData, ISimulatabl } var uNames = new List(); - for (int colIdx = 0; colIdx< unitData.U.GetNColumns(); colIdx++) + for (int colIdx = 0; colIdx < unitData.U.GetNColumns(); colIdx++) { var uName = "U" + colIdx; inputData.Add(uName, unitData.U.GetColumn(colIdx)); @@ -514,9 +556,9 @@ static private (bool, double[]) SimulateSingle(UnitDataSet unitData, ISimulatabl modelCopy.SetOutputID("output"); PlantSimulator sim = new PlantSimulator(new List { modelCopy }); - // var simData = new TimeSeriesDataSet(); + // var simData = new TimeSeriesDataSet(); var isOk = sim.SimulateSingle(inputData, singleModelName, false, out var simData); - if(!isOk) + if (!isOk) return (false, null); double[] y_sim = simData.GetValues(singleModelName, SignalType.Output_Y); if (noiseAmplitude > 0) @@ -556,7 +598,7 @@ static private (bool, double[]) SimulateSingle(UnitDataSet unitData, ISimulatabl /// /// /// - public bool SimulateSingle(TimeSeriesDataSet inputData, string singleModelName, + public bool SimulateSingle(TimeSeriesDataSet inputData, string singleModelName, bool doCalcYwithoutAdditiveTerms, out TimeSeriesDataSet simData) { if (!modelDict.ContainsKey(singleModelName)) @@ -632,7 +674,7 @@ public bool SimulateSingle(TimeSeriesDataSet inputData, string singleModelName, return false; } } - var isOk = simData.AddDataPoint(nameOfSimulatedSignal, timeIdx, outputVal[0]); + var isOk = simData.AddDataPoint(nameOfSimulatedSignal, timeIdx, outputVal[0]); if (!isOk) { return false; @@ -641,8 +683,8 @@ public bool SimulateSingle(TimeSeriesDataSet inputData, string singleModelName, if (inputData.GetTimeStamps() != null) simData.SetTimeStamps(inputData.GetTimeStamps().ToList()); else - { - //? + { + //? } // disturbance estimation if (modelDict[singleModelName].GetProcessModelType() == ModelType.SubProcess && doEstimateDisturbance) @@ -669,7 +711,7 @@ public bool SimulateSingle(TimeSeriesDataSet inputData, string singleModelName, /// the external signals for the simulation(also, determines the simulation time span and timebase) /// the simulated data set to be outputted(excluding the external signals) /// - public bool Simulate (TimeSeriesDataSet inputData, out TimeSeriesDataSet simData) + public bool Simulate(TimeSeriesDataSet inputData, out TimeSeriesDataSet simData) { var timeBase_s = inputData.GetTimeBase(); ; @@ -684,21 +726,21 @@ public bool Simulate (TimeSeriesDataSet inputData, out TimeSeriesDataSet simData { if (!modelDict.ElementAt(i).Value.IsModelSimulatable(out string explStr)) { - Shared.GetParserObj().AddError("PlantSimulator could not run, model "+ - modelDict.ElementAt(i).Key + " lacks all required inputs to be simulatable:"+ + Shared.GetParserObj().AddError("PlantSimulator could not run, model " + + modelDict.ElementAt(i).Key + " lacks all required inputs to be simulatable:" + explStr); simData = null; return false; } } - (var orderedSimulatorIDs,var compLoopDict) = connections.InitAndDetermineCalculationOrderOfModels(modelDict); + (var orderedSimulatorIDs, var compLoopDict) = connections.InitAndDetermineCalculationOrderOfModels(modelDict); simData = new TimeSeriesDataSet(); // initalize the new time-series to be created in simData. var init = new PlantSimulatorInitalizer(this); - var didInit = init.ToSteadyStateAndEstimateDisturbances(ref inputData, ref simData, compLoopDict) ; + var didInit = init.ToSteadyStateAndEstimateDisturbances(ref inputData, ref simData, compLoopDict); if (!didInit) { Shared.GetParserObj().AddError("PlantSimulator failed to initalize."); @@ -713,14 +755,14 @@ public bool Simulate (TimeSeriesDataSet inputData, out TimeSeriesDataSet simData string[] inputIDs = model.GetBothKindsOfInputIDs(); if (inputIDs == null) { - Shared.GetParserObj().AddError("PlantSimulator.Simulate() failed. Model \""+ model.GetID() + + Shared.GetParserObj().AddError("PlantSimulator.Simulate() failed. Model \"" + model.GetID() + "\" has null inputIDs."); return false; } - double[] inputVals = GetValuesFromEitherDataset(inputIDs, timeIdx,simData,inputData); + double[] inputVals = GetValuesFromEitherDataset(inputIDs, timeIdx, simData, inputData); - string outputID = model.GetOutputID(); - if (outputID==null) + string outputID = model.GetOutputID(); + if (outputID == null) { Shared.GetParserObj().AddError("PlantSimulator.Simulate() failed. Model \"" + model.GetID() + "\" has null outputID."); @@ -752,12 +794,12 @@ public bool Simulate (TimeSeriesDataSet inputData, out TimeSeriesDataSet simData { var model = modelDict[orderedSimulatorIDs.ElementAt(modelIdx)]; string[] inputIDs = model.GetBothKindsOfInputIDs(); - int inputDataLookBackIdx = 0; + 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,inputData); + double[] inputVals = GetValuesFromEitherDataset(inputIDs, lastGoodTimeIndex - inputDataLookBackIdx, simData, inputData); if (inputVals == null) { Shared.GetParserObj().AddError("PlantSimulator.Simulate() failed. Model \"" + model.GetID() + @@ -765,18 +807,18 @@ public bool Simulate (TimeSeriesDataSet inputData, out TimeSeriesDataSet simData return false; } double[] outputVal = model.Iterate(inputVals, timeBase_s); - bool isOk = simData.AddDataPoint(model.GetOutputID(),timeIdx,outputVal[0]); + bool isOk = simData.AddDataPoint(model.GetOutputID(), timeIdx, outputVal[0]); if (!isOk) { - Shared.GetParserObj().AddError("PlantSimulator.Simulate() failed. Unable to add data point for \"" + Shared.GetParserObj().AddError("PlantSimulator.Simulate() failed. Unable to add data point for \"" + model.GetOutputID() + "\", indicating an error in initalization. "); return false; } if (outputVal.Length > 1) { - if (timeIdx ==0) + if (timeIdx == 0) { - simData.InitNewSignal(model.GetID(), outputVal[1],N.Value); + simData.InitNewSignal(model.GetID(), outputVal[1], N.Value); } bool isOk2 = simData.AddDataPoint(model.GetID(), timeIdx, outputVal[1]); if (!isOk2) @@ -785,49 +827,12 @@ public bool Simulate (TimeSeriesDataSet inputData, out TimeSeriesDataSet simData } } simData.SetTimeStamps(inputData.GetTimeStamps().ToList()); + PlantFitScore = FitScoreCalculator.GetPlantWideSimulated(this, inputData, simData); return true; } - /// - /// - /// - /// - /// - /// - /// - /// - private double[] GetValuesFromEitherDataset(string[] inputIDs, int timeIndex, - TimeSeriesDataSet dataSet1, TimeSeriesDataSet dataSet2) - { - double[] retVals = new double[inputIDs.Length]; - - int index = 0; - foreach (var inputId in inputIDs) - { - double? retVal=null; - if (dataSet1.ContainsSignal(inputId)) - { - retVal = dataSet1.GetValue(inputId, timeIndex); - } - else if (dataSet2.ContainsSignal(inputId)) - { - retVal= dataSet2.GetValue(inputId, timeIndex); - } - if (!retVal.HasValue) - { - retVals[index] = Double.NaN; - } - else - { - retVals[index] = retVal.Value; - } - - index++; - } - return retVals; - } /// /// Creates a JSON text string serialization of this object @@ -836,7 +841,7 @@ private double[] GetValuesFromEitherDataset(string[] inputIDs, int timeIndex, public string SerializeTxt() { var settings = new JsonSerializerSettings(); - settings.TypeNameHandling = TypeNameHandling.Auto; + settings.TypeNameHandling = TypeNameHandling.Auto; settings.Formatting = Formatting.Indented; // models outputs that are not connected to anyting are "null" @@ -856,16 +861,16 @@ public string SerializeTxt() /// /// the desired file name and plant name(can be null, in which case the filename should be given in the path argument) /// create file in the given path - public bool Serialize(string newPlantName = null, string path= null) + public bool Serialize(string newPlantName = null, string path = null) { string fileName = ""; if (path != null) { fileName = path; if (!fileName.EndsWith(@"\")) - fileName += @"\"; + fileName += @"\"; } - if (newPlantName!=null) + if (newPlantName != null) { fileName += newPlantName; } @@ -886,6 +891,57 @@ public bool Serialize(string newPlantName = null, string path= null) return fileWriter.Close(); } + /// + /// Writes the plant information in a human-friendly format + /// + /// + public override string ToString() + { + var writeCulture = new CultureInfo("en-US"); + var numberFormat = (System.Globalization.NumberFormatInfo)writeCulture.NumberFormat.Clone(); + numberFormat.NumberDecimalSeparator = "."; + + int sDigits = 3; + + StringBuilder sb = new StringBuilder(); + sb.AppendLine(this.GetType().ToString()); + sb.AppendLine("-------------------------"); + sb.AppendLine("Name: " + plantName); + sb.AppendLine("Description: " + plantDescription); + sb.AppendLine("Date: " + date); + sb.AppendLine(""); + + foreach (var model in modelDict) + { + if (model.Value.GetOutputIdentID()== null) + sb.AppendLine("model:" + model.Key + " with output: " + model.Value.GetOutputID()); + else + sb.AppendLine("model:" + model.Key + " with output: " + model.Value.GetOutputID() +" ident.against:" + model.Value.GetOutputIdentID()); + + } + foreach (var connection in connections.connections) + { + sb.AppendLine("connection:" + connection.Item1 + " to " + connection.Item2); + } + foreach (var signal in externalInputSignalIDs) + { + sb.AppendLine("external signals:" + signal); + } + + sb.AppendLine(""); + + if (double.IsNaN(PlantFitScore)) + { + sb.AppendLine("Plant Fit Score: not avaiable, most likely because inputData did not contain the measured outputs required to calculate it."); + } + else + sb.AppendLine("Plant Fit Score: " + SignificantDigits.Format(PlantFitScore, sDigits)); + + return sb.ToString(); + + } + + } } diff --git a/Dynamic/SimulatableModels/GainSchedParameters.cs b/Dynamic/SimulatableModels/GainSchedParameters.cs index f861ba0..35e3c8c 100644 --- a/Dynamic/SimulatableModels/GainSchedParameters.cs +++ b/Dynamic/SimulatableModels/GainSchedParameters.cs @@ -185,7 +185,7 @@ public void MoveOperatingPointUWithoutChangingModel(double newOperatingPointU) /// public void SetOperatingPoint(double newOperatingPointU, double newOperatingPointY) { - OperatingPoint_Y = newOperatingPointU; + OperatingPoint_U = newOperatingPointU; OperatingPoint_Y = newOperatingPointY; } diff --git a/TimeSeriesAnalysis.Tests/Tests/DisturbanceEstimatorTests.cs b/TimeSeriesAnalysis.Tests/Tests/DisturbanceEstimatorTests.cs index 85edf94..553cbce 100644 --- a/TimeSeriesAnalysis.Tests/Tests/DisturbanceEstimatorTests.cs +++ b/TimeSeriesAnalysis.Tests/Tests/DisturbanceEstimatorTests.cs @@ -140,7 +140,7 @@ public void GenericDisturbanceTest (UnitModel processModel, double[] trueDistur var pidDataSet = plantSim.GetUnitDataSetForPID(inputData.Combine(simData), pidModel1); var result = DisturbanceIdentifier.EstimateDisturbance(pidDataSet, processModel); - // Console.WriteLine(result.); + Console.WriteLine(plantSim.ToString()); if (doAssertResult) { @@ -218,6 +218,9 @@ public void DisturbanceTestUsingPlantSimulator(UnitModel processModel, double[] ////////////////////////////////// Assert.IsTrue(isOK); Assert.IsTrue(simDataSetWithDisturbance.ContainsSignal(distSignal)); + // + Console.WriteLine(plantSim.ToString()); + if (doAssertResult) { var pidDataSet = plantSim.GetUnitDataSetForPID(inputData.Combine(simDataSetWithDisturbance), pidModel1); diff --git a/TimeSeriesAnalysis.Tests/Tests/GainSchedIdentifyTests.cs b/TimeSeriesAnalysis.Tests/Tests/GainSchedIdentifyTests.cs index b7274b9..49e35ea 100644 --- a/TimeSeriesAnalysis.Tests/Tests/GainSchedIdentifyTests.cs +++ b/TimeSeriesAnalysis.Tests/Tests/GainSchedIdentifyTests.cs @@ -405,13 +405,12 @@ public void NonzeroOperatingPointU_Both_EstimatesStillOk(double uOperatingPoint, } else { - GainSchedFittingSpecs gsFittingSpecs = new GainSchedFittingSpecs { uGainThresholds = new double[] { gainSchedThreshold } }; + GainSchedFittingSpecs gsFittingSpecs = new GainSchedFittingSpecs { uGainThresholds = new double[] { gainSchedThreshold },DoSetOperatingPointToDatasetMean =false }; idModel = GainSchedIdentifier.IdentifyForGivenThresholds(unitData, gsFittingSpecs); } // plot - bool doPlot = false; - if (doPlot) + if (false) { Shared.EnablePlots(); Plot.FromList(new List { diff --git a/TimeSeriesAnalysis.csproj b/TimeSeriesAnalysis.csproj index e6528ee..3270909 100644 --- a/TimeSeriesAnalysis.csproj +++ b/TimeSeriesAnalysis.csproj @@ -14,7 +14,7 @@ False https://github.com/equinor/TimeSeriesAnalysis.git readme.md - 1.3.36 + 1.3.37 Equinor Equinor true