fable 3.1.36 → 3.1.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fable",
3
- "version": "3.1.36",
3
+ "version": "3.1.38",
4
4
  "description": "A service dependency injection, configuration and logging library.",
5
5
  "main": "source/Fable.js",
6
6
  "scripts": {
@@ -404,6 +404,11 @@
404
404
  "Address": "fable.Dates.dateFromParts"
405
405
  },
406
406
 
407
+ "slice": {
408
+ "Name": "Slice Array",
409
+ "Address": "fable.Utility.slice"
410
+ },
411
+
407
412
  "createvalueobjectbyhashes": {
408
413
  "Name": "Create Value Object by Hashes",
409
414
  "Address": "fable.Utility.createValueObjectByHashes"
@@ -441,5 +446,9 @@
441
446
  "gaussianelimination": {
442
447
  "Name": "Solve a System of Linear Equations using Gaussian Elimination",
443
448
  "Address": "fable.Math.gaussianElimination"
449
+ },
450
+ "predict": {
451
+ "Name": "Predict Y Values from X Values using a Regression Model",
452
+ "Address": "fable.Math.predictFromRegressionModel"
444
453
  }
445
454
  }
@@ -247,13 +247,14 @@ class FableServiceExpressionParser extends libFableServiceBase
247
247
  this.lintTokenizedExpression(tmpResultsObject.RawTokens, tmpResultsObject);
248
248
  this.buildPostfixedSolveList(tmpResultsObject.RawTokens, tmpResultsObject);
249
249
 
250
+ const tmpManifest = (typeof(pManifest) === 'object') ? pManifest : this.fable.newManyfest();
250
251
  if (tmpResultsObject.SolverDirectives.Code == 'SERIES')
251
252
  {
252
253
  const [ tmpStep , tmpFrom, tmpTo] = this._prepareDirectiveParameters([
253
254
  tmpResultsObject.SolverDirectives.Step,
254
255
  tmpResultsObject.SolverDirectives.From,
255
256
  tmpResultsObject.SolverDirectives.To,
256
- ], [ '1' ], tmpResultsObject, tmpDataSourceObject, pManifest);
257
+ ], [ '1' ], tmpResultsObject, tmpDataSourceObject, tmpManifest);
257
258
 
258
259
  if (isNaN(tmpFrom) || isNaN(tmpTo))
259
260
  {
@@ -305,17 +306,19 @@ class FableServiceExpressionParser extends libFableServiceBase
305
306
 
306
307
  for (let i = 0; i <= tmpIterations; i++)
307
308
  {
308
- let tmpCurrentValueOfN = this.fable.Math.addPrecise(tmpFrom, this.fable.Math.multiplyPrecise(tmpStep, i.toString()));
309
+ const tmpCurrentValueOfN = this.fable.Math.addPrecise(tmpFrom, this.fable.Math.multiplyPrecise(tmpStep, i.toString()));
310
+ const tmpPreviousValueOfN = (i == 0) ? 'false' : this.fable.Math.addPrecise(tmpFrom, this.fable.Math.multiplyPrecise(tmpStep, (i - 1).toString()));
309
311
 
310
312
  // Jimmy up the data source with the current N value, stepIndex and all the other data from the source object
311
313
  // This generates a data source object every time on purpose so we can remarshal in values that changed in the destination
312
314
  let tmpSeriesStepDataSourceObject = Object.assign({}, tmpDataSourceObject);
313
315
  tmpSeriesStepDataSourceObject.n = tmpCurrentValueOfN;
316
+ tmpSeriesStepDataSourceObject.prev_n = tmpPreviousValueOfN;
314
317
  tmpSeriesStepDataSourceObject.stepIndex = i;
315
318
 
316
- let tmpMutatedValues = this.substituteValuesInTokenizedObjects(tmpResultsObject.PostfixTokenObjects, tmpSeriesStepDataSourceObject, tmpResultsObject, pManifest);
319
+ let tmpMutatedValues = this.substituteValuesInTokenizedObjects(tmpResultsObject.PostfixTokenObjects, tmpSeriesStepDataSourceObject, tmpResultsObject, tmpManifest);
317
320
 
318
- tmpValueArray.push( this.solvePostfixedExpression( tmpResultsObject.PostfixSolveList, tmpDataDestinationObject, tmpResultsObject, pManifest) );
321
+ tmpValueArray.push( this.solvePostfixedExpression( tmpResultsObject.PostfixSolveList, tmpDataDestinationObject, tmpResultsObject, tmpManifest) );
319
322
 
320
323
  for (let j = 0; j < tmpMutatedValues.length; j++)
321
324
  {
@@ -330,7 +333,6 @@ class FableServiceExpressionParser extends libFableServiceBase
330
333
  tmpAssignmentManifestHash = tmpResultsObject.OriginalRawTokens[0];
331
334
  }
332
335
 
333
- let tmpManifest = (typeof(pManifest) === 'object') ? pManifest : this.fable.newManyfest();
334
336
  tmpManifest.setValueByHash(tmpDataDestinationObject, tmpAssignmentManifestHash, tmpValueArray);
335
337
 
336
338
  return tmpValueArray;
@@ -348,7 +350,7 @@ class FableServiceExpressionParser extends libFableServiceBase
348
350
  const tmpVariableDescription = tmpDirectiveValues[tmpVariableKey];
349
351
 
350
352
  // Get the actual value for this variable's address
351
- tmpVariableDescription.Value = pManifest.getValueByHash(tmpDataSourceObject, tmpVariableDescription.Address);
353
+ tmpVariableDescription.Value = tmpManifest.getValueByHash(tmpDataSourceObject, tmpVariableDescription.Address);
352
354
  }
353
355
 
354
356
  // If the first value doesn't have keys, don't do the map.
@@ -366,11 +368,12 @@ class FableServiceExpressionParser extends libFableServiceBase
366
368
  // Jimmy up the data source with the current N value, stepIndex and all the other data from the source object
367
369
  // This generates a data source object every time on purpose so we can remarshal in values that changed in the destination
368
370
  let tmpSeriesStepDataSourceObject = Object.assign({}, tmpDataSourceObject);
371
+ tmpSeriesStepDataSourceObject.stepIndex = i;
369
372
 
370
373
  for (let j = 0; j < tmpDirectiveValueKeys.length; j++)
371
374
  {
372
375
  const tmpVariableKey = tmpDirectiveValueKeys[j];
373
- if (!Array.isArray(tmpDirectiveValues[tmpVariableKey].Value) || (tmpDirectiveValues[tmpVariableKey].Value.length <= j))
376
+ if (!Array.isArray(tmpDirectiveValues[tmpVariableKey].Value) || (tmpDirectiveValues[tmpVariableKey].Value.length <= i))
374
377
  {
375
378
  tmpSeriesStepDataSourceObject[tmpVariableKey] = 0;
376
379
  }
@@ -378,11 +381,24 @@ class FableServiceExpressionParser extends libFableServiceBase
378
381
  {
379
382
  tmpSeriesStepDataSourceObject[tmpVariableKey] = tmpDirectiveValues[tmpVariableKey].Value[i];
380
383
  }
384
+ const tmpPreviousValueKey = `prev_${tmpVariableKey}`;
385
+ if (!Array.isArray(tmpDirectiveValues[tmpVariableKey].Value) || (tmpDirectiveValues[tmpVariableKey].Value.length <= i))
386
+ {
387
+ tmpSeriesStepDataSourceObject[tmpPreviousValueKey] = 0;
388
+ }
389
+ else if (i == 0)
390
+ {
391
+ tmpSeriesStepDataSourceObject[tmpPreviousValueKey] = 'false';
392
+ }
393
+ else
394
+ {
395
+ tmpSeriesStepDataSourceObject[tmpPreviousValueKey] = tmpDirectiveValues[tmpVariableKey].Value[i - 1];
396
+ }
381
397
  }
382
398
 
383
- let tmpMutatedValues = this.substituteValuesInTokenizedObjects(tmpResultsObject.PostfixTokenObjects, tmpSeriesStepDataSourceObject, tmpResultsObject, pManifest);
399
+ let tmpMutatedValues = this.substituteValuesInTokenizedObjects(tmpResultsObject.PostfixTokenObjects, tmpSeriesStepDataSourceObject, tmpResultsObject, tmpManifest);
384
400
 
385
- tmpValueArray.push( this.solvePostfixedExpression( tmpResultsObject.PostfixSolveList, tmpDataDestinationObject, tmpResultsObject, pManifest) );
401
+ tmpValueArray.push( this.solvePostfixedExpression( tmpResultsObject.PostfixSolveList, tmpDataDestinationObject, tmpResultsObject, tmpManifest) );
386
402
 
387
403
  for (let j = 0; j < tmpMutatedValues.length; j++)
388
404
  {
@@ -397,7 +413,6 @@ class FableServiceExpressionParser extends libFableServiceBase
397
413
  tmpAssignmentManifestHash = tmpResultsObject.OriginalRawTokens[0];
398
414
  }
399
415
 
400
- let tmpManifest = (typeof(pManifest) === 'object') ? pManifest : this.fable.newManyfest();
401
416
  tmpManifest.setValueByHash(tmpDataDestinationObject, tmpAssignmentManifestHash, tmpValueArray);
402
417
 
403
418
  return tmpValueArray;
@@ -406,7 +421,7 @@ class FableServiceExpressionParser extends libFableServiceBase
406
421
  {
407
422
  const [ tmpSampleCount ] = this._prepareDirectiveParameters([
408
423
  tmpResultsObject.SolverDirectives.SampleCount
409
- ], [ '1' ], tmpResultsObject, tmpDataSourceObject, pManifest);
424
+ ], [ '1' ], tmpResultsObject, tmpDataSourceObject, tmpManifest);
410
425
 
411
426
  if (isNaN(tmpSampleCount))
412
427
  {
@@ -424,7 +439,7 @@ class FableServiceExpressionParser extends libFableServiceBase
424
439
  {
425
440
  let tmpVariableKey = tmpVariableKeys[i];
426
441
  let tmpVariableDescription = tmpMonteCarloOutput.Values[tmpVariableKey];
427
-
442
+
428
443
  // For each variable, generate its array of sampled values
429
444
  tmpVariableDescription.Distribution = {};
430
445
  tmpVariableDescription.ValueSequence = [];
@@ -437,7 +452,7 @@ class FableServiceExpressionParser extends libFableServiceBase
437
452
  let tmpPointValue = this.fable.Math.parsePrecise(tmpPointToken, NaN);
438
453
  if (isNaN(tmpPointValue) && typeof tmpPointToken === 'string' && tmpPointToken.length > 0)
439
454
  {
440
- tmpPointValue = pManifest.getValueByHash(tmpDataSourceObject, tmpPointToken);
455
+ tmpPointValue = tmpManifest.getValueByHash(tmpDataSourceObject, tmpPointToken);
441
456
  if (!tmpPointValue || (tmpPointValue == null))
442
457
  {
443
458
  //TODO: Warn?
@@ -497,8 +512,8 @@ class FableServiceExpressionParser extends libFableServiceBase
497
512
  // tmpPreviousDomainTranslationAmount = this.fable.Math.addPrecise(tmpPreviousDomainTranslationAmount, tmpVariableDescription.DomainLength);
498
513
  // }
499
514
  }
500
-
501
- for (let i = 0; i <= tmpSampleCount - 1; i++)
515
+
516
+ for (let i = 0; i <= Number(tmpSampleCount) - 1; i++)
502
517
  {
503
518
  // Jimmy up the data source with the current N value, stepIndex and all the other data from the source object
504
519
  // This generates a data source object every time on purpose so we can remarshal in values that changed in the destination
@@ -528,8 +543,8 @@ class FableServiceExpressionParser extends libFableServiceBase
528
543
  tmpPointManifest.Distribution[tmpDistributionPointValue] = tmpPointManifest.Distribution[tmpDistributionPointValue] + 1;
529
544
  }
530
545
 
531
- let tmpMutatedValues = this.substituteValuesInTokenizedObjects(tmpResultsObject.PostfixTokenObjects, tmpSeriesStepDataSourceObject, tmpResultsObject, pManifest);
532
- tmpMonteCarloOutput.Samples.push( this.solvePostfixedExpression( tmpResultsObject.PostfixSolveList, tmpDataDestinationObject, tmpResultsObject, pManifest ) );
546
+ let tmpMutatedValues = this.substituteValuesInTokenizedObjects(tmpResultsObject.PostfixTokenObjects, tmpSeriesStepDataSourceObject, tmpResultsObject, tmpManifest);
547
+ tmpMonteCarloOutput.Samples.push( this.solvePostfixedExpression( tmpResultsObject.PostfixSolveList, tmpDataDestinationObject, tmpResultsObject, tmpManifest ) );
533
548
 
534
549
  for (let j = 0; j < tmpMutatedValues.length; j++)
535
550
  {
@@ -544,7 +559,6 @@ class FableServiceExpressionParser extends libFableServiceBase
544
559
  tmpAssignmentManifestHash = tmpResultsObject.OriginalRawTokens[0];
545
560
  }
546
561
 
547
- let tmpManifest = (typeof(pManifest) === 'object') ? pManifest : this.fable.newManyfest();
548
562
  tmpManifest.setValueByHash(tmpDataDestinationObject, tmpAssignmentManifestHash, tmpMonteCarloOutput);
549
563
 
550
564
  return tmpMonteCarloOutput;
@@ -173,8 +173,8 @@ class FableServiceMath extends libFableServiceBase
173
173
 
174
174
  /**
175
175
  * Adds two values precisely.
176
- * @param {number} pLeftValue - The left value to be added.
177
- * @param {number} pRightValue - The right value to be added.
176
+ * @param {number|string} pLeftValue - The left value to be added.
177
+ * @param {number|string} pRightValue - The right value to be added.
178
178
  * @returns {string} - The result of adding the two values as a string.
179
179
  */
180
180
  addPrecise(pLeftValue, pRightValue)
@@ -190,8 +190,8 @@ class FableServiceMath extends libFableServiceBase
190
190
  /**
191
191
  * Subtracts two values precisely.
192
192
  *
193
- * @param {number} pLeftValue - The left value to subtract.
194
- * @param {number} pRightValue - The right value to subtract.
193
+ * @param {number|string} pLeftValue - The left value to subtract.
194
+ * @param {number|string} pRightValue - The right value to subtract.
195
195
  * @returns {string} The result of the subtraction as a string.
196
196
  */
197
197
  subtractPrecise(pLeftValue, pRightValue)
@@ -1556,7 +1556,7 @@ class FableServiceMath extends libFableServiceBase
1556
1556
  */
1557
1557
  leastSquares(pIndependentVariableVectors, pDependentVariableVector)
1558
1558
  {
1559
- const tmpIndependentVariableVectors = Array.isArray(pIndependentVariableVectors[0]) ? pIndependentVariableVectors : pIndependentVariableVectors.map(value => [value]);
1559
+ const tmpIndependentVariableVectors = Array.isArray(pIndependentVariableVectors[0]) ? this.matrixTranspose(pIndependentVariableVectors) : pIndependentVariableVectors.map(value => [value]);
1560
1560
  // Add bias term (intercept)
1561
1561
  const tmpIndependentVariableMatrixWithBiasTerm = tmpIndependentVariableVectors.map(row => [1, ...row]);
1562
1562
 
@@ -1756,6 +1756,27 @@ class FableServiceMath extends libFableServiceBase
1756
1756
  return this.addPrecise(pEasingConfiguration.DomainRangeStart, tmpScaledValue);
1757
1757
  }
1758
1758
  }
1759
+
1760
+ /**
1761
+ * Predicts the dependent variable using a regression model.
1762
+ *
1763
+ * @param {Array<number|string>} pRegressionCoefficients - The regression coefficients [b0, b1, ..., bn].
1764
+ * @param {Array<number|string>|number|string} pIndependentVariableVector - The independent variable values [x1, x2, ..., xn] or single value for single variable.
1765
+ *
1766
+ * @return {number|string} - The predicted dependent variable value.
1767
+ */
1768
+ predictFromRegressionModel(pRegressionCoefficients, pIndependentVariableVector)
1769
+ {
1770
+ let tmpIndependentVariableVector = pIndependentVariableVector;
1771
+ if (!Array.isArray(pIndependentVariableVector))
1772
+ {
1773
+ tmpIndependentVariableVector = [ pIndependentVariableVector ];
1774
+ }
1775
+ return pRegressionCoefficients.slice(1).reduce((sum, b, i) =>
1776
+ {
1777
+ return this.addPrecise(sum, this.multiplyPrecise(b, pIndependentVariableVector[i]));
1778
+ }, pRegressionCoefficients[0]);
1779
+ }
1759
1780
  }
1760
1781
 
1761
1782
  module.exports = FableServiceMath;
@@ -190,9 +190,21 @@ class FableServiceUtility extends libFableServiceBase
190
190
  return tmpValueArray;
191
191
  }
192
192
 
193
+ slice(pValueArray, pStartIndex, pEndIndex)
194
+ {
195
+ if (!Array.isArray(pValueArray))
196
+ {
197
+ this.fable.log.error('Fable.Math.slice called with non-array value', { pValueArray });
198
+ return [];
199
+ }
200
+ const tmpStartIndex = Number(this.fable.Math.parsePrecise(pStartIndex, '0'));
201
+ const tmpEndIndex = Number(this.fable.Math.parsePrecise(pEndIndex, pValueArray.length.toString()));
202
+ return pValueArray.slice(tmpStartIndex, tmpEndIndex);
203
+ }
204
+
193
205
  /**
194
206
  * Get a value array by hash/address list from the internal fable/pict state
195
- * @param {string} pValueAddress - The manyfest hash/address of the value to get
207
+ * @param {string} pValueHashes - The manyfest hash/address of the value to get
196
208
  * @param {object} [pManifest] - The manyfest object to use; constructs one inline if not provided
197
209
  * @returns {Array} - The value array built from the hash list
198
210
  */
@@ -215,7 +227,7 @@ class FableServiceUtility extends libFableServiceBase
215
227
  /**
216
228
  * Get a value object from a list of hash/addressese
217
229
  * @param {object} pObject - The object to get the value from
218
- * @param {string} pValueAddress - The manyfest hash/address of the value to get
230
+ * @param {string} pValueHashes - The manyfest hash/address of the value to get
219
231
  * @param {object} [pManifest] - The manyfest object to use; constructs one inline if not provided
220
232
  * @returns {object} - The value object built from the hash list
221
233
  */
@@ -188,6 +188,9 @@ suite
188
188
  Expect(tmpDestinationObject.EGS).to.equal(tmpDestinationObject.EGSR);
189
189
  testFable.ExpressionParser.Messaging.logFunctionSolve(tmpResultObject);
190
190
  Expect(tmpDestinationObject.EGS).to.equal("0.0061");
191
+ _Parser.solve('MATH_DP=ROUND(1.2345, 5 - 2)',
192
+ tmpDataObject, tmpResultObject, false, tmpDestinationObject);
193
+ Expect(tmpDestinationObject.MATH_DP).to.equal("1.235");
191
194
 
192
195
  tmpDataObject.EJBinderGb1 = '15.5';
193
196
  tmpDataObject.EKPctBinderPb2 = '2.55555';
@@ -475,7 +478,7 @@ suite
475
478
 
476
479
  let tmpManifest = testFable.newManyfest();
477
480
  let tmpDataSourceObject = {};
478
- tmpDataSourceObject.CitiesData = require('./data/Cities.json');
481
+ tmpDataSourceObject.CitiesData = require('./data/cities.json');
479
482
  /* Records look like:
480
483
  [
481
484
  {
@@ -873,6 +876,9 @@ suite
873
876
 
874
877
  _Parser.solve('PreciseEquals = If("1.0000000001", "===", "1", "yes", "no")', testFable, tmpResultsObject, false, tmpDestinationObject);
875
878
  Expect(tmpDestinationObject.PreciseEquals).to.equal('no');
879
+
880
+ _Parser.solve('Computed = If(AppData.Cities[0].latitude, "<", "50", AppData.Cities[0].latitude - 25, AppData.Cities[0].latitude + 25)', testFable, tmpResultsObject, false, tmpDestinationObject);
881
+ Expect(tmpDestinationObject.Computed).to.equal(testFable.AppData.Cities[0].latitude - 25);
876
882
  }
877
883
  );
878
884
 
@@ -899,6 +905,234 @@ suite
899
905
  testFable.log.info('Series From Coefficients Result:', testFable.AppData.SeriesFromCoefficients);
900
906
  _Parser.solve('IntegratedSeries = SUM(FLATTEN(AppData.SeriesFromCoefficients))', testFable, testFable.AppData, false, testFable.AppData);
901
907
  testFable.log.info('Integrated Series Result:', testFable.AppData.IntegratedSeries);
908
+
909
+ const tmpRawData =
910
+ {
911
+ "StandardSelector": "English",
912
+ "MaxDryDensityTable": [
913
+ {
914
+ "MaxDryDensityTable": {
915
+ "f": "4.19",
916
+ "g": "125.7",
917
+ "j": "0.19",
918
+ "l": "109.1",
919
+ "NTest": "",
920
+ "a": "",
921
+ "b": "",
922
+ "d": "13.51",
923
+ "e": "9.32",
924
+ "h": "1.44",
925
+ "i": "1.25",
926
+ "k": "15.2"
927
+ },
928
+ "MaxDryDensityTablec": "10.00"
929
+ },
930
+ {
931
+ "MaxDryDensityTable": {
932
+ "f": "4.01",
933
+ "g": "120.3",
934
+ "j": "0.16",
935
+ "l": "106.5",
936
+ "NTest": "",
937
+ "a": "",
938
+ "b": "",
939
+ "d": "13.34",
940
+ "e": "9.33",
941
+ "h": "1.39",
942
+ "i": "1.23",
943
+ "k": "13"
944
+ },
945
+ "MaxDryDensityTablec": "11.00"
946
+ },
947
+ {
948
+ "MaxDryDensityTable": {
949
+ "f": "4.19",
950
+ "g": "125.7",
951
+ "j": "0.22",
952
+ "l": "106",
953
+ "NTest": "",
954
+ "a": "",
955
+ "b": "",
956
+ "d": "13.51",
957
+ "e": "9.32",
958
+ "h": "1.40",
959
+ "i": "1.18",
960
+ "k": "18.6"
961
+ },
962
+ "MaxDryDensityTablec": "12.00"
963
+ }
964
+ ],
965
+ "NuclearTable": [
966
+ {
967
+ "NuclearTable": {},
968
+ "NDD": "0",
969
+ "ADD": "0",
970
+ "NuclearPercentPR": "0"
971
+ }
972
+ ],
973
+ "Pulverization": [
974
+ {
975
+ "PTestNo": "1"
976
+ }
977
+ ],
978
+ "FMC": [
979
+ {
980
+ "MassOfWater": "0"
981
+ }
982
+ ],
983
+ "MetaTemplate": {},
984
+ "Header": {},
985
+ "SM": {
986
+ "SF": "0",
987
+ "SI": "0",
988
+ "SJ": "0"
989
+ },
990
+ "NM": {},
991
+ "Chart": {
992
+ "Slope": "1.18181818181818181818",
993
+ "Intercept": "91.136363636363636363664",
994
+ "MCZeroAtDD": "21.830887491264849755",
995
+ "ShiftToParallel": "3.230887491264849755",
996
+ "WetSideY2": "0.84615384615384615385",
997
+ "WetSideY1": "-36.84746008708272859272",
998
+ "DomainBegin": "13",
999
+ "DomainEnd": "18.6",
1000
+ "WetSideY0": "-6240",
1001
+ "OptimalMoistureContent": "16.27122843513086489567",
1002
+ "DryCount": 16,
1003
+ "WetCount": "16",
1004
+ "TotalCount": "32",
1005
+ "StartPlot": "12",
1006
+ "XValues": [
1007
+ "13.2",
1008
+ "13.4",
1009
+ "13.6",
1010
+ "13.8",
1011
+ "14",
1012
+ "14.2",
1013
+ "14.4",
1014
+ "14.6",
1015
+ "14.8",
1016
+ "15",
1017
+ "15.2",
1018
+ "15.4",
1019
+ "15.6",
1020
+ "15.8",
1021
+ "16",
1022
+ "16.2",
1023
+ "16.4",
1024
+ "16.6",
1025
+ "16.8",
1026
+ "17",
1027
+ "17.2",
1028
+ "17.4",
1029
+ "17.6",
1030
+ "17.8",
1031
+ "18",
1032
+ "18.2",
1033
+ "18.4",
1034
+ "18.6",
1035
+ "18.8",
1036
+ "19",
1037
+ "19.2",
1038
+ "19.4",
1039
+ ],
1040
+ "PrimaryRoot": "110.3659972415182948767",
1041
+ "WetCountIntermediate": "43",
1042
+ "ValueLimit": "28"
1043
+ },
1044
+ "Result": "",
1045
+ "FamilyOfCurvesZone": "0.99",
1046
+ "MaxDryDensityEnglish": "0",
1047
+ "MaxDryDensityMetric": "0",
1048
+ "DryX": "15.2",
1049
+ "DryY": "109.1",
1050
+ "AsIsX": "13",
1051
+ "AsIsY": "106.5",
1052
+ "WetX": "18.6",
1053
+ "WetY": "106",
1054
+ "AverageMaxDryDensity": "11",
1055
+ "OptimumMoistureOfTotalMaterial": "0.1",
1056
+ "om": "0.0",
1057
+ "pr": "0.0"
1058
+ };
1059
+
1060
+ const tmpBaseMoistures = tmpRawData.Chart.XValues;
1061
+ //TODO: compute this form above; 3 stages; dry, wet, combined
1062
+ const tmpCombinedDensities = [ '106.7363636','106.9727273','107.2090909','107.4454545','107.6818182','107.9181818',
1063
+ '108.1545455','108.3909091','108.6272727','108.8636364','109.1','109.3363636','109.5727273','109.8090909','110.0454545',
1064
+ '110.2818182','110.1152028','109.7279363','109.3433842','108.9615182','108.5823101','108.2057322','107.8317574',
1065
+ '107.4603587','107.0915096','106.7251839','106.3613559','106','105.6410912','105.2846046','104.9305159','104.5788009'
1066
+ ];
1067
+ const tmpMatrix = [];
1068
+ for (let i = 1; i <= 10; ++i)
1069
+ {
1070
+ const tmpPowArray = [];
1071
+ for (let j = 0; j < tmpBaseMoistures.length; ++j)
1072
+ {
1073
+ tmpPowArray.push(testFable.Math.powerPrecise(tmpBaseMoistures[j], i));
1074
+ }
1075
+ tmpMatrix.push(tmpPowArray);
1076
+ }
1077
+ testFable.AppData.FitToDensities = tmpCombinedDensities;
1078
+ testFable.AppData.MoistureMatrix = tmpMatrix;
1079
+ testFable.log.info('Fit To Densities:', testFable.AppData.FitToDensities);
1080
+ testFable.log.info('Moisture Matrix:', testFable.AppData.MoistureMatrix);
1081
+ _Parser.solve('LinearRegressionHand = LINEST(AppData.MoistureMatrix, AppData.FitToDensities)', testFable, testFable.AppData, false, testFable.AppData);
1082
+ testFable.log.info('Density Regression Coefficients:', testFable.AppData.LinearRegressionHand);
1083
+
1084
+ const tmpSolverInstructions =
1085
+ [
1086
+ 'ChartDomainBegin = MaxDryDensityTable[0].MaxDryDensityTable.k - 2',
1087
+ 'ChartDomainEnd = ChartDomainBegin + (Chart.TotalCount - 1) * 0.2',
1088
+ 'XValues = SERIES FROM ChartDomainBegin TO ChartDomainEnd STEP 0.2 : n + 0',
1089
+ 'DryCount = MATCH(Chart.OptimalMoistureContent, XValues)',
1090
+ 'WetCountIntermediate = 60 - DryCount',
1091
+ 'WetCount = IF(DryCount, "<=", 30, DryCount, WetCountIntermediate)',
1092
+ 'TotalCount = DryCount + WetCount',
1093
+ 'StartPlot = DryCount - 5',
1094
+ 'ChartValueLimit = Chart.XValues.length - 1',
1095
+ 'DryValues = MAP VAR x FROM Chart.XValues : x * Chart.Slope + Chart.Intercept',
1096
+ 'WetValues = MAP VAR x FROM Chart.XValues : 6240 / (x + 100 / 2.7 + Chart.ShiftToParallel)',
1097
+ 'CombinedValues = MAP VAR dry FROM DryValues VAR wet FROM WetValues VAR x from XValues : IF(x, "LTE", Chart.OptimalMoistureContent, dry, wet)',
1098
+ 'FilteredXValues = MAP VAR x FROM XValues : IF(ABS(Chart.OptimalMoistureContent - x), "LT", 1, x, 0)',
1099
+ 'XValueMatrix = createarrayfromabsolutevalues(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)', // Create Placeholders
1100
+ 'XValueMatrix[0] = MAP VAR x FROM XValues : x + 0',
1101
+ 'XValueMatrix[1] = MAP VAR x FROM XValues : x^2',
1102
+ 'XValueMatrix[2] = MAP VAR x FROM XValues : x^3',
1103
+ 'XValueMatrix[3] = MAP VAR x FROM XValues : x^4',
1104
+ 'XValueMatrix[4] = MAP VAR x FROM XValues : x^5',
1105
+ 'XValueMatrix[5] = MAP VAR x FROM XValues : x^6',
1106
+ 'XValueMatrix[6] = MAP VAR x FROM XValues : x^7',
1107
+ 'XValueMatrix[7] = MAP VAR x FROM XValues : x^8',
1108
+ 'XValueMatrix[8] = MAP VAR x FROM XValues : x^9',
1109
+ 'XValueMatrix[9] = MAP VAR x FROM XValues : x^10',
1110
+ 'LinearRegression = LINEST(XValueMatrix, CombinedValues)',
1111
+ 'FilteredXValueVectors = MatrixTranspose(XValueMatrix)',
1112
+ 'FittedDensities = MAP VAR x FROM XValues VAR vector FROM FilteredXValueVectors : PREDICT(LinearRegression, vector)',
1113
+ 'FilteredDensities = MAP VAR x FROM FilteredXValues VAR prediction FROM FittedDensities : IF(x, "==", 0, 0, prediction)',
1114
+ ];
1115
+ const tmpResultsObject = {};
1116
+ for (const tmpInstruction of tmpSolverInstructions)
1117
+ {
1118
+ _Parser.solve(tmpInstruction, tmpRawData, tmpResultsObject, false, tmpRawData);
1119
+ testFable.log.info(`After instruction: ${tmpInstruction}`, tmpRawData[tmpInstruction.split('=')[0].trim()]);
1120
+ }
1121
+
1122
+ Expect(tmpRawData.FilteredDensities[10]).to.equal('0');
1123
+ Expect(Number(tmpRawData.FilteredDensities[11])).to.be.closeTo(109.3, 0.1);
1124
+ Expect(Number(tmpRawData.FilteredDensities[20])).to.be.closeTo(108.6, 0.1);
1125
+ Expect(tmpRawData.FilteredDensities[21]).to.equal('0');
1126
+
1127
+ /*
1128
+ testFable.log.info('linest payload (hand):', { MoistureMatrix: testFable.AppData.MoistureMatrix, FitToDensities: testFable.AppData.FitToDensities });
1129
+ testFable.log.info('linest payload (solver):', { XValueMatrix: tmpRawData.XValueMatrix, CombinedValues: tmpRawData.CombinedValues });
1130
+
1131
+ testFable.log.info('coefficients (hand):', testFable.AppData.LinearRegressionHand);
1132
+ testFable.log.info('coefficients (solver):', tmpRawData.LinearRegression);
1133
+
1134
+ testFable.log.info('filtered densities (solver):', tmpRawData.FilteredDensities);
1135
+ */
902
1136
  }
903
1137
  );
904
1138
  }
package/test/Math_test.js CHANGED
@@ -486,25 +486,33 @@ suite
486
486
  testFable.log.info('Prediction for [2,1]:', predict(coeffs_1, [2.5]));
487
487
 
488
488
  // Example: predict y from x1 and x2
489
+ /* Example from: https://mathforcollege.com/nm/mws/gen/06reg/mws_gen_reg_spe_multivariate.pdf
490
+ 144 18 52
491
+ 142 24 40
492
+ 124 12 40
493
+ 64 30 48
494
+ 96 30 32
495
+ 92 22 16
496
+ */
489
497
  const X = [
490
- [1, 2],
491
- [2, 0],
492
- [3, 1],
493
- [4, 3]
498
+ [ 18, 24, 12, 30, 30, 22 ],
499
+ [ 52, 40, 40, 48, 32, 16 ],
494
500
  ];
495
501
 
496
- const y = [3, 2, 4, 5];
502
+ const y = [ 144, 142, 124, 64, 96, 92 ];
497
503
 
498
504
  const coeffs = testFable.Math.leastSquares(X, y);
499
505
  testFable.log.info('Coefficients:', coeffs);
506
+ Expect(coeffs.length).to.equal(3); // intercept + 2 variables
507
+ Expect(Number(coeffs[0])).to.be.closeTo(150.166, 0.01);
508
+ Expect(Number(coeffs[1])).to.be.closeTo(-2.731, 0.01);
509
+ Expect(Number(coeffs[2])).to.be.closeTo(0.581, 0.01);
500
510
 
501
- testFable.log.info('Prediction for [1,2] (training value is 3):', predict(coeffs, [1, 2]));
502
- testFable.log.info('Prediction for [2,0] (training value is 2):', predict(coeffs, [2, 0]));
503
- testFable.log.info('Prediction for [3,1] (training value is 4):', predict(coeffs, [3, 1]));
504
- Expect(Number(predict(coeffs, [3, 1]))).to.be.closeTo(4, 0.51);
505
- testFable.log.info('Prediction for [4,3] (training value is 5):', predict(coeffs, [4, 3]));
506
- Expect(Number(predict(coeffs, [4, 3]))).to.be.closeTo(5, 0.25);
507
- testFable.log.info('Prediction for [2,1]:', predict(coeffs, [2, 1]));
511
+ testFable.log.info('Prediction for [18,52] (training value is 144):', predict(coeffs, [18, 52]));
512
+ Expect(Number(predict(coeffs, [18, 52]))).to.be.closeTo(144, 15);
513
+
514
+ testFable.log.info('Prediction for [22,16] (training value is 92):', predict(coeffs, [22, 16]));
515
+ Expect(Number(predict(coeffs, [22, 16]))).to.be.closeTo(92, 10);
508
516
  }
509
517
  );
510
518