Skip to content

Commit 602e892

Browse files
committed
Simple skeleton for GainSchedIdentify
1 parent c09bc70 commit 602e892

11 files changed

+2400
-26
lines changed
+396
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,396 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
using TimeSeriesAnalysis.Utility;
8+
9+
namespace TimeSeriesAnalysis.Dynamic
10+
{
11+
/// <summary>
12+
/// The data for a porition of a process, containg only one output and one or multiple inputs that influence it
13+
/// </summary>
14+
public class GainSchedDataSet
15+
{
16+
/// <summary>
17+
/// list of warings during identification
18+
/// </summary>
19+
public List<GainSchedWarnings> Warnings { get; set; }
20+
/// <summary>
21+
/// Name
22+
/// </summary>
23+
public string ProcessName { get; }
24+
/// <summary>
25+
/// Timestamps
26+
/// </summary>
27+
public DateTime[] Times { get; set; }
28+
/// <summary>
29+
/// Output Y (measured)
30+
/// </summary>
31+
public double[] Y_meas { get; set; }
32+
/// <summary>
33+
/// Output Y (simulated)
34+
/// </summary>
35+
public double[] Y_sim { get; set; }
36+
37+
/// <summary>
38+
/// Input U(simulated) - in the case of PID-control
39+
/// </summary>
40+
public double[,] U_sim { get; set; }
41+
42+
/// <summary>
43+
/// Setpoint - (if sub-process includes a PID-controller)
44+
/// </summary>
45+
public double[] Y_setpoint { get; set; } = null;
46+
47+
/// <summary>
48+
/// Additve output disturbance D (Y = X+ D)
49+
/// </summary>
50+
public double[] D { get; set; }
51+
52+
/// <summary>
53+
/// Input U (given)
54+
/// </summary>
55+
public double[,] U { get; set; }
56+
57+
/// <summary>
58+
/// Indices that are ignored in Y during fitting.
59+
/// </summary>
60+
public List<int> IndicesToIgnore = null;
61+
62+
/// <summary>
63+
/// Some systems for storing data do not support "NaN", but instead some other magic
64+
/// value is reserved for indicating that a value is bad or missing.
65+
/// </summary>
66+
public double BadDataID { get; set; } = -9999;
67+
68+
69+
/// <summary>
70+
/// Constructor for data set without inputs - for "autonomous" processes such as sinusoids,
71+
/// rand walks or other disturbancs.
72+
/// </summary>
73+
/// <param name="name">optional internal name of dataset</param>
74+
public GainSchedDataSet(string name = null)
75+
{
76+
this.Warnings = new List<GainSchedWarnings>();
77+
this.Y_meas = null;
78+
this.U = null;
79+
this.ProcessName = name;
80+
}
81+
82+
/// <summary>
83+
/// Create a copy of an existing data set
84+
/// </summary>
85+
/// <param name="otherDataSet"></param>
86+
public GainSchedDataSet(GainSchedDataSet otherDataSet)
87+
{
88+
this.ProcessName = otherDataSet.ProcessName + "copy";
89+
90+
if (otherDataSet.Y_meas == null)
91+
this.Y_meas = null;
92+
else
93+
this.Y_meas = otherDataSet.Y_meas.Clone() as double[];
94+
if (otherDataSet.Y_setpoint == null)
95+
this.Y_setpoint = null;
96+
else
97+
this.Y_setpoint = otherDataSet.Y_setpoint.Clone() as double[];
98+
99+
if (otherDataSet.Y_sim == null)
100+
{
101+
this.Y_sim = null;
102+
}
103+
else
104+
{
105+
this.Y_sim = otherDataSet.Y_sim.Clone() as double[];
106+
}
107+
if (otherDataSet.U == null)
108+
this.U = null;
109+
else
110+
this.U = otherDataSet.U.Clone() as double[,];
111+
if (otherDataSet.U_sim == null)
112+
this.U_sim = null;
113+
else
114+
this.U_sim = otherDataSet.U_sim.Clone() as double[,];
115+
if (otherDataSet.Times == null)
116+
this.Times = null;
117+
else
118+
this.Times = otherDataSet.Times.Clone() as DateTime[];
119+
if (otherDataSet.D == null)
120+
this.D = null;
121+
else
122+
this.D = otherDataSet.D.Clone() as double[];
123+
124+
if (otherDataSet.IndicesToIgnore == null)
125+
this.IndicesToIgnore = null;
126+
else
127+
this.IndicesToIgnore = new List<int>(otherDataSet.IndicesToIgnore);
128+
129+
this.BadDataID = otherDataSet.BadDataID;
130+
}
131+
132+
/// <summary>
133+
/// Create a downsampled copy of an existing data set
134+
/// </summary>
135+
/// <param name="originalDataSet"></param>
136+
/// <param name="downsampleFactor">factor by which to downsample the original dataset</param>
137+
public GainSchedDataSet(GainSchedDataSet originalDataSet, int downsampleFactor)
138+
{
139+
this.ProcessName = originalDataSet.ProcessName + "downsampledFactor" + downsampleFactor;
140+
141+
this.Y_meas = Vec<double>.Downsample(originalDataSet.Y_meas, downsampleFactor);
142+
this.Y_setpoint = Vec<double>.Downsample(originalDataSet.Y_setpoint, downsampleFactor);
143+
this.Y_sim = Vec<double>.Downsample(originalDataSet.Y_sim, downsampleFactor);
144+
this.U = Array2D<double>.Downsample(originalDataSet.U, downsampleFactor);
145+
this.U_sim = Array2D<double>.Downsample(originalDataSet.U_sim, downsampleFactor);
146+
this.Times = Vec<DateTime>.Downsample(originalDataSet.Times, downsampleFactor);
147+
}
148+
149+
public int GetNumDataPoints ()
150+
{
151+
if (U != null)
152+
return U.GetNRows();
153+
else if (Times != null)
154+
return Times.Length;
155+
else if (Y_meas != null)
156+
return Y_meas.Length;
157+
else if (Y_setpoint!= null)
158+
return Y_setpoint.Length;
159+
else
160+
return 0;
161+
}
162+
163+
/// Tags indices to be removed if either of the output is outside the range defined by
164+
/// [Y_min,Y_max], an input is outside [u_min, umax] or if any data matches badDataId
165+
///
166+
public void DetermineIndicesToIgnore(FittingSpecs fittingSpecs)
167+
{
168+
if (fittingSpecs == null)
169+
{
170+
return;
171+
}
172+
var newIndToExclude = new List<int>();
173+
var vec = new Vec();
174+
175+
// find values below minimum for each input
176+
if (fittingSpecs.Y_min_fit.HasValue)
177+
{
178+
if (!Double.IsNaN(fittingSpecs.Y_min_fit.Value) && fittingSpecs.Y_min_fit.Value != BadDataID
179+
&& !Double.IsNegativeInfinity(fittingSpecs.Y_min_fit.Value))
180+
{
181+
var indices =
182+
vec.FindValues(Y_meas, fittingSpecs.Y_min_fit.Value, VectorFindValueType.SmallerThan, IndicesToIgnore);
183+
newIndToExclude.AddRange(indices);
184+
}
185+
}
186+
if (fittingSpecs.Y_max_fit.HasValue)
187+
{
188+
if (!Double.IsNaN(fittingSpecs.Y_max_fit.Value) && fittingSpecs.Y_max_fit.Value != BadDataID
189+
&& !Double.IsPositiveInfinity(fittingSpecs.Y_max_fit.Value))
190+
{
191+
var indices =
192+
vec.FindValues(Y_meas, fittingSpecs.Y_max_fit.Value, VectorFindValueType.BiggerThan, IndicesToIgnore);
193+
newIndToExclude.AddRange(indices);
194+
}
195+
}
196+
// find values below minimum for each input
197+
if (fittingSpecs.U_min_fit != null)
198+
{
199+
for (int idx = 0; idx < Math.Min(fittingSpecs.U_min_fit.Length, U.GetNColumns()); idx++)
200+
{
201+
if (Double.IsNaN(fittingSpecs.U_min_fit[idx]) || fittingSpecs.U_min_fit[idx] == BadDataID
202+
|| Double.IsNegativeInfinity(fittingSpecs.U_min_fit[idx]))
203+
continue;
204+
var indices =
205+
vec.FindValues(U.GetColumn(idx), fittingSpecs.U_min_fit[idx], VectorFindValueType.SmallerThan, IndicesToIgnore);
206+
newIndToExclude.AddRange(indices);
207+
}
208+
}
209+
if (fittingSpecs.U_max_fit != null)
210+
{
211+
for (int idx = 0; idx < Math.Min(fittingSpecs.U_max_fit.Length, U.GetNColumns()); idx++)
212+
{
213+
if (Double.IsNaN(fittingSpecs.U_max_fit[idx]) || fittingSpecs.U_max_fit[idx] == BadDataID
214+
|| Double.IsNegativeInfinity(fittingSpecs.U_max_fit[idx]))
215+
continue;
216+
var indices =
217+
vec.FindValues(U.GetColumn(idx), fittingSpecs.U_max_fit[idx],
218+
VectorFindValueType.BiggerThan, IndicesToIgnore);
219+
newIndToExclude.AddRange(indices);
220+
}
221+
}
222+
if (newIndToExclude.Count > 0)
223+
{
224+
var result = Vec<int>.Sort(newIndToExclude.ToArray(), VectorSortType.Ascending);
225+
newIndToExclude = result.ToList();
226+
var newIndToExcludeDistinct = newIndToExclude.Distinct();
227+
newIndToExclude = newIndToExcludeDistinct.ToList();
228+
}
229+
230+
if (IndicesToIgnore != null)
231+
{
232+
if (newIndToExclude.Count > 0)
233+
{
234+
IndicesToIgnore.AddRange(newIndToExclude);
235+
}
236+
}
237+
else
238+
{
239+
IndicesToIgnore = newIndToExclude;
240+
}
241+
}
242+
243+
244+
/// <summary>
245+
/// Tags indices to be removed if either of the inputs are outside the range defined by
246+
/// [uMinFit,uMaxFit].
247+
///
248+
/// uMinFit,uMaxFit may include NaN or BadDataID for values if no max/min applies to the specific input
249+
/// </summary>
250+
/// <param name="uMinFit">vector of minimum values for each element in U</param>
251+
/// <param name="uMaxFit">vector of maximum values for each element in U</param>
252+
public void SetInputUFitMaxAndMin(double[] uMinFit, double[] uMaxFit)
253+
{
254+
if ((uMinFit == null && uMaxFit == null) || this.U == null)
255+
return;
256+
257+
var newIndToExclude = new List<int>();
258+
var vec = new Vec();
259+
// find values below minimum for each input
260+
if (uMinFit != null)
261+
{
262+
for (int idx = 0; idx < Math.Min(uMinFit.Length,U.GetNColumns()); idx++)
263+
{
264+
if (Double.IsNaN(uMinFit[idx]) || uMinFit[idx] == BadDataID || Double.IsNegativeInfinity(uMinFit[idx]))
265+
continue;
266+
var indices =
267+
vec.FindValues(U.GetColumn(idx), uMinFit[idx], VectorFindValueType.SmallerThan, IndicesToIgnore);
268+
newIndToExclude.AddRange(indices);
269+
}
270+
}
271+
if (uMaxFit != null)
272+
{
273+
for (int idx = 0; idx < Math.Min(uMaxFit.Length, U.GetNColumns()); idx++)
274+
{
275+
if (Double.IsNaN(uMaxFit[idx]) || uMaxFit[idx] == BadDataID || Double.IsNegativeInfinity(uMaxFit[idx]))
276+
continue;
277+
var indices =
278+
vec.FindValues(U.GetColumn(idx), uMaxFit[idx], VectorFindValueType.BiggerThan, IndicesToIgnore);
279+
newIndToExclude.AddRange(indices);
280+
}
281+
}
282+
if (newIndToExclude.Count > 0)
283+
{
284+
var result = Vec<int>.Sort(newIndToExclude.ToArray(), VectorSortType.Ascending);
285+
newIndToExclude = result.ToList();
286+
var newIndToExcludeDistinct = newIndToExclude.Distinct();
287+
newIndToExclude = newIndToExcludeDistinct.ToList();
288+
}
289+
290+
if (IndicesToIgnore != null)
291+
{
292+
if (newIndToExclude.Count > 0)
293+
{
294+
IndicesToIgnore.AddRange(newIndToExclude);
295+
}
296+
}
297+
IndicesToIgnore = newIndToExclude;
298+
}
299+
300+
/// <summary>
301+
/// Gets the time between samples in seconds, returns zero if times are not set
302+
/// </summary>
303+
/// <returns></returns>
304+
public double GetTimeBase()
305+
{
306+
if (Times != null)
307+
{
308+
if (Times.Length > 2)
309+
return (Times.Last() - Times.First()).TotalSeconds / (Times.Length - 1);
310+
else
311+
return 0;
312+
}
313+
return 0;
314+
}
315+
public void CreateTimeStamps(double timeBase_s, DateTime? t0 = null)
316+
{
317+
if (t0 == null)
318+
{
319+
t0 = new DateTime(2010, 1, 1);//intended for testing
320+
}
321+
322+
var times = new List<DateTime>();
323+
//times.Add(t0.Value);
324+
DateTime time = t0.Value;
325+
for (int i = 0; i < GetNumDataPoints(); i++)
326+
{
327+
times.Add(time);
328+
time = time.AddSeconds(timeBase_s);
329+
}
330+
this.Times = times.ToArray();
331+
}
332+
333+
/// <summary>
334+
/// Create a dataset for single-input system from two signals that have separate but overlapping
335+
/// time-series(each given as value-date tuples)
336+
/// </summary>
337+
/// <param name="u">tuple of values and dates describing u</param>
338+
/// <param name="y_meas">tuple of values and dates describing y</param>
339+
/// <param name="name">name of dataset</param>
340+
public GainSchedDataSet((double[], DateTime[]) u, (double[], DateTime[]) y_meas, string name = null/*,
341+
int? timeBase_s=null*/)
342+
{
343+
var jointTime = Vec<DateTime>.Intersect(u.Item2.ToList(),y_meas.Item2.ToList());
344+
var indU = Vec<DateTime>.GetIndicesOfValues(u.Item2.ToList(), jointTime);
345+
var indY = Vec<DateTime>.GetIndicesOfValues(y_meas.Item2.ToList(), jointTime);
346+
this.Times = jointTime.ToArray();
347+
348+
this.Y_meas = Vec<double>.GetValuesAtIndices(y_meas.Item1,indY);
349+
var newU = Vec<double>.GetValuesAtIndices(u.Item1, indU);
350+
this.U = Array2D<double>.CreateFromList(new List<double[]> { newU });
351+
this.ProcessName = name;
352+
}
353+
354+
/// <summary>
355+
/// Get the time spanned by the dataset
356+
/// </summary>
357+
/// <returns>The time spanned by the dataset, or null if times are not set</returns>
358+
public TimeSpan GetTimeSpan()
359+
{
360+
if (this.Times == null)
361+
{
362+
return new TimeSpan();
363+
}
364+
if (this.Times.Length == 0)
365+
{
366+
return new TimeSpan();
367+
}
368+
return Times.Last() - Times.First();
369+
}
370+
/// <summary>
371+
/// Get the average value of each input in the dataset.
372+
/// This is useful when defining model local around a working point.
373+
/// </summary>
374+
/// <returns>an array of averages, each corrsponding to one column of U.
375+
/// Returns null if it was not possible to calculate averages</returns>
376+
public double[] GetAverageU()
377+
{
378+
if (U == null)
379+
{
380+
return null;
381+
}
382+
List<double> averages = new List<double>();
383+
384+
for (int i = 0; i < U.GetNColumns(); i++)
385+
{
386+
double? avg = (new Vec(BadDataID)).Mean(U.GetColumn(i));
387+
if (!avg.HasValue)
388+
return null;
389+
averages.Add(avg.Value);
390+
}
391+
return averages.ToArray();
392+
}
393+
394+
395+
}
396+
}

0 commit comments

Comments
 (0)