Skip to content

Commit

Permalink
Merge branch 'master' into GainSchedIdentify
Browse files Browse the repository at this point in the history
# Conflicts:
#	Dynamic/Identification/FittingInfo.cs
#	TimeSeriesAnalysis.Tests/Tests/PlantSimulatorMISOTests.cs
#	TimeSeriesAnalysis.Tests/Tests/SysIDUnitTests.cs
  • Loading branch information
Steinar Elgsæter committed Jan 30, 2024
2 parents d875db2 + ca2aa38 commit a49cd11
Show file tree
Hide file tree
Showing 20 changed files with 734 additions and 195 deletions.
4 changes: 2 additions & 2 deletions Dynamic/Identification/ClosedLoopUnitIdentifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public class ClosedLoopUnitIdentifier
fittingSpecs.u0 = u0;


int nGains=1;
//int nGains=1;
// ----------------
// run1: no process model assumed, let disturbance estimator guesstimate a pid-process gain,
// to give afirst estimate of the disturbance
Expand All @@ -130,7 +130,7 @@ public class ClosedLoopUnitIdentifier

dataSet1.D = distIdResult1.d_est;
var unitModel_run1 = id.IdentifyLinearAndStatic(ref dataSet1, fittingSpecs, doTimeDelayEstOnRun1);
nGains = unitModel_run1.modelParameters.GetProcessGains().Length;
// nGains = unitModel_run1.modelParameters.GetProcessGains().Length;
idDisturbancesList.Add(distIdResult1);
idUnitModelsList.Add(unitModel_run1);
isOK = ClosedLoopSim(dataSet1, unitModel_run1.GetModelParameters(), pidParams, distIdResult1.d_est, "run1");
Expand Down
225 changes: 225 additions & 0 deletions Dynamic/Identification/FitScore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

using TimeSeriesAnalysis.Dynamic;

namespace TimeSeriesAnalysis
{
public class FitScore
{


/// <summary>
/// Calculate a "fit score" between two signals.
/// </summary>
/// <param name="meas"></param>
/// <param name="sim"></param>
/// <returns> a fit score that is maximum 100 percent, but can also go negative if fit is poor</returns>
public static double Calc(double[] meas, double[] sim)
{
if (meas == null)
return double.NaN;
if (sim == null)
return double.NaN;

double dev = Deviation(meas, sim);
double devFromSelf = DeviationFromAvg(meas);

double fitScore = 0;
if ( !Double.IsNaN(dev))
{
fitScore = (double)(1 - dev / devFromSelf) * 100;
}
else
{
fitScore = double.NaN;
}
return fitScore;
}

/// <summary>
/// Determines a score for how well a simulation fits with inputData. This can
/// be compared over time to see any signs of degredation of the plant over time
///
/// </summary>
/// <param name="plantSimObj"></param>
/// <param name="inputData"></param>
/// <param name="simData"></param>
/// <returns></returns>
static public double GetPlantWideSimulated(PlantSimulator plantSimObj, TimeSeriesDataSet inputData,
TimeSeriesDataSet simData)
{
const string disturbanceSignalPrefix = "_D";
List<double> fitScores = new List<double>();
foreach (var modelName in plantSimObj.modelDict.Keys)
{
double[] measY = null;
double[] simY = null;

var modelObj = plantSimObj.modelDict[modelName];
var outputName = modelObj.GetOutputID();
var outputIdentName = modelObj.GetOutputIdentID();
if (outputName == "" || outputName == null)
continue;

if (plantSimObj.modelDict[modelName].GetProcessModelType() == ModelType.PID)
{
if (inputData.ContainsSignal(outputName))
{
measY = inputData.GetValues(outputName);
}

}
else //if (plantSimObj.modelDict[modelName].GetProcessModelType() == ModelType.SubProcess)
{
if (outputIdentName != null)
{
if (inputData.ContainsSignal(outputIdentName))
{
measY = inputData.GetValues(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)
{
if (!modelObj.GetAdditiveInputIDs()[0].StartsWith(disturbanceSignalPrefix))
{
measY = inputData.GetValues(outputName);
}
}
else if (inputData.ContainsSignal(outputName))
{
measY = inputData.GetValues(outputName);
}
}
if (simData.ContainsSignal(outputName))
{
simY = simData.GetValues(outputName);
}

if (measY != null && simY != null)
{
var curFitScore = FitScore.Calc(measY, simY);

if (curFitScore != double.NaN)
{
fitScores.Add(curFitScore);
}
}

}
if (!fitScores.Any())
return double.NaN;
else
{
return fitScores.Average();
}

}



/// <summary>
/// Get the average stored fit-scores in the "paramters.Fitting" object,
/// indicating how well the plant on average described/aligned with the dataset when it was fitted.
/// </summary>
/// <param name="plantSimObj"></param>
/// <returns></returns>
static public double GetPlantWideStored(PlantSimulator plantSimObj)
{
List<double> fitScores = new List<double>();
foreach (var modelName in plantSimObj.modelDict.Keys)
{
double curFitScore = 0;
if (plantSimObj.modelDict[modelName].GetProcessModelType() == ModelType.PID)
{
var modelObj = (PidModel)plantSimObj.modelDict[modelName];
if (modelObj.pidParameters.Fitting != null)
{
curFitScore = modelObj.pidParameters.Fitting.FitScorePrc;
}
}
else if (plantSimObj.modelDict[modelName].GetProcessModelType() == ModelType.SubProcess)
{
var modelObj = (UnitModel)plantSimObj.modelDict[modelName];
if (modelObj.modelParameters.Fitting != null)
{
curFitScore = modelObj.modelParameters.Fitting.FitScorePrc;
}
}
if (curFitScore != 0)
{
fitScores.Add(curFitScore);
}

}
if (!fitScores.Any())
return double.NaN;
else
{
return fitScores.Average();
}
}


/// <summary>
/// Get the absolute average deviation between the referene and model signals
/// </summary>
/// <param name="reference"></param>
/// <param name="model"></param>
/// <returns></returns>
private static double Deviation(double[] reference, double[] model)
{
if (reference == null)
return double.NaN;
if (model == null)
return double.NaN;
if (reference.Length == 0)
return double.NaN;
if (model.Length == 0)
return double.NaN;
double ret = 0;
double N = 0;
for (var i = 0; i < Math.Min(reference.Length, model.Length); i++)
{
ret += Math.Abs(reference[i] - model[i]);
N++;
}
if (N == 0)
return 0;
ret = ret / N;
return ret;
}

private static double DeviationFromAvg(double[] signal)
{
if (signal == null) return double.NaN;
if (signal.Length == 0) return double.NaN;

var vec = new Vec();
var avg = vec.Mean(signal);

if (!avg.HasValue)
return double.NaN;

var N = signal.Length;
double ret = 0;
for (var i = 0; i < N; i++)
{
ret += Math.Abs(signal[i] - avg.Value);
}
return ret / N;
}


}
}
21 changes: 15 additions & 6 deletions Dynamic/Identification/FittingInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,27 @@ public class FittingInfo

public double ObjFunValAbs { get; set; }


/// <summary>
/// A score that is 100% if model describes all variations
/// and 0% if model is no better at describing variation than the flat average line.
/// Negative if the model is worse than a flat average line.
/// </summary>

public double FitScorePrc { get; set; }

/// <summary>
/// Number of bad data points ignored during fitting
/// </summary>
///

public double NFittingBadDataPoints { get; set; }

/// <summary>
/// Number of total data points (good and bad) available for fitting
/// </summary>
public double NFittingTotalDataPoints { get; set; }


/// <summary>
/// Start time of fitting data set
/// </summary>
Expand All @@ -85,14 +95,13 @@ public class FittingInfo
public DateTime EndTime { get; set; }




/// <summary>
/// NB! this code seems to have an error with negative rsqdiff for cases when there yIndicesToIgnore is not empty.
/// It may be preferable to use the output of the regression, as this avoids duplicating logic.
/// </summary>
/// <param name="dataSet"></param>
/// <param name="yIndicesToIgnore"></param>

public void CalcCommonFitMetricsFromDataset(UnitDataSet dataSet, List<int> yIndicesToIgnore)
{
Vec vec = new Vec(dataSet.BadDataID);
Expand Down Expand Up @@ -140,11 +149,11 @@ public void CalcCommonFitMetricsFromDataset(UnitDataSet dataSet, List<int> yIndi
{
this.ObjFunValDiff = Double.NaN;
}

// this.NFittingBadDataPoints = yIndicesToIgnore.Count;

this.NFittingBadDataPoints = yIndicesToIgnore.Count;


var fitScore = FitScore.Calc(ymeas_vals, ysim_vals);
this.FitScorePrc = SignificantDigits.Format(fitScore, nDigits);

}
}
Expand Down
3 changes: 2 additions & 1 deletion Dynamic/Identification/PidIdentifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,8 @@ private double[] GetErrorTerm(UnitDataSet dataSet, PidFilter pidFilter)
pidParam.Fitting.NFittingBadDataPoints = regressResults.NfittingBadDataPoints;
pidParam.Fitting.RsqDiff = regressResults.Rsq;
pidParam.Fitting.ObjFunValDiff = regressResults.ObjectiveFunctionValue;

pidParam.Fitting.FitScorePrc = SignificantDigits.Format(FitScore.Calc(dataSet.U.GetColumn(0), U_sim.GetColumn(0)), nDigits);

pidParam.Fitting.ObjFunValAbs = vec.SumOfSquareErr(dataSet.U.GetColumn(0), U_sim.GetColumn(0), 0);
pidParam.Fitting.RsqAbs = vec.RSquared(dataSet.U.GetColumn(0), U_sim.GetColumn(0), null, 0) * 100;

Expand Down
7 changes: 7 additions & 0 deletions Dynamic/Interfaces/ISimulateableModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@ public interface ISimulatableModel
/// <returns></returns>
string GetOutputID();

/// <summary>
/// Get the ID of the "OutputIdent" signal
/// </summary>
/// <returns></returns>
string GetOutputIdentID();


/// <summary>
/// Set the output ID
/// </summary>
Expand Down
12 changes: 12 additions & 0 deletions Dynamic/Interfaces/ModelBaseClass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,18 @@ public virtual string GetOutputID()
return this.outputID;
}


/// <summary>
/// returns the ID of the signal the output is identified against
/// </summary>
/// <returns> may return <c>null</c> if output is not set</returns>
public virtual string GetOutputIdentID()
{
return this.outputIdentID;
}



/// <summary>
/// Get the length of the output vector
/// </summary>
Expand Down
6 changes: 6 additions & 0 deletions Dynamic/PlantSimulator/PlantSimulator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ public class PlantSimulator
/// </summary>
public List<Comment> comments;

/// <summary>
/// The date of when the model was last saved
/// </summary>
public DateTime date { get; set; }


public Dictionary<string, ISimulatableModel> modelDict;
public List<string> externalInputSignalIDs;
public ConnectionParser connections;
Expand Down
Loading

0 comments on commit a49cd11

Please sign in to comment.