fable 3.1.35 → 3.1.37

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.
@@ -461,6 +461,68 @@ suite
461
461
  Expect(parseFloat(tmpResult)).to.be.at.most(13.78);
462
462
 
463
463
 
464
+ return fDone();
465
+ }
466
+ );
467
+ test
468
+ (
469
+ 'Test map operation',
470
+ (fDone) =>
471
+ {
472
+ let testFable = new libFable();
473
+ let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
474
+
475
+
476
+ let tmpManifest = testFable.newManyfest();
477
+ let tmpDataSourceObject = {};
478
+ tmpDataSourceObject.CitiesData = require('./data/Cities.json');
479
+ /* Records look like:
480
+ [
481
+ {
482
+ "city": "New York",
483
+ "growth_from_2000_to_2013": "4.8%",
484
+ "latitude": 40.7127837,
485
+ "longitude": -74.0059413,
486
+ "population": "8405837",
487
+ "rank": "1",
488
+ "state": "New York"
489
+ },
490
+ {
491
+ "city": "Los Angeles",
492
+ "growth_from_2000_to_2013": "4.8%",
493
+ "latitude": 34.0522342,
494
+ "longitude": -118.2436849,
495
+ "population": "3884307",
496
+ "rank": "2",
497
+ "state": "California"
498
+ },
499
+ */
500
+
501
+ tmpDataSourceObject.MapTest = {};
502
+ tmpDataSourceObject.MapTest.Values = [ 1, 2, 3, 4, 5 ];
503
+
504
+ let tmpDataDestinationObject = {};
505
+ let tmpParserResultsObject = {};
506
+
507
+ let tmpResult = _Parser.solve('SimpleMapResult = MAP VAR x FROM MapTest.Values : x + 100', tmpDataSourceObject, tmpParserResultsObject, tmpManifest, tmpDataDestinationObject);
508
+
509
+ // Should expect 101 -> 105
510
+ Expect(tmpDataDestinationObject.SimpleMapResult.length).to.equal(5);
511
+ Expect(tmpDataDestinationObject.SimpleMapResult[0]).to.equal("101");
512
+ Expect(tmpDataDestinationObject.SimpleMapResult[1]).to.equal("102");
513
+ Expect(tmpDataDestinationObject.SimpleMapResult[2]).to.equal("103");
514
+ Expect(tmpDataDestinationObject.SimpleMapResult[3]).to.equal("104");
515
+ Expect(tmpDataDestinationObject.SimpleMapResult[4]).to.equal("105");
516
+
517
+ let tmpComplexMapResult = _Parser.solve('ComplexMapResult = MAP VAR cityRecord FROM CitiesData VAR x FROM MapTest.Values : cityRecord.population + (x * 1000000000000000)', tmpDataSourceObject, tmpParserResultsObject, tmpManifest, tmpDataDestinationObject);
518
+
519
+ Expect(tmpDataDestinationObject.ComplexMapResult.length).to.equal(1000);
520
+ Expect(tmpDataDestinationObject.ComplexMapResult[0]).to.equal("1000000008405837");
521
+ Expect(tmpDataDestinationObject.ComplexMapResult[4]).to.equal("5000000001553165");
522
+ // We ran out of values 1-5 so now it's just population
523
+ Expect(tmpDataDestinationObject.ComplexMapResult[10]).to.equal("885400");
524
+ Expect(tmpDataDestinationObject.ComplexMapResult[400]).to.equal("81050");
525
+
464
526
  return fDone();
465
527
  }
466
528
  );
@@ -519,8 +581,8 @@ suite
519
581
  {
520
582
  let testFable = new libFable();
521
583
  let _Parser = testFable.instantiateServiceProviderIfNotExists('ExpressionParser');
522
-
523
584
  let tmpManifest = testFable.newManyfest();
585
+
524
586
  let tmpDataSourceObject = {};
525
587
  let tmpDataDestinationObject = {};
526
588
  let tmpParserResultsObject = {};
@@ -532,6 +594,46 @@ suite
532
594
  Expect(tmpResult).to.exist;
533
595
  Expect(tmpResult.Samples.length).to.equal(1000);
534
596
 
597
+ // Rosenbrock function test (experimenting with non-deterministic test cases)
598
+ /* Interesting values:
599
+
600
+ | Purpose | (x) | (y) | (z) | Notes |
601
+ | ----------------------------------------- | ----------------- | ----------------- | ----------------- | ------------------------------------------------------------------------------- |
602
+ | **Exploration (broad view)** | ([-3, 3]) | ([-1, 5]) | ([-1, 5]) | Shows how the function grows large and steep outside the valley. |
603
+ | **Focused optimization testing** | ([0, 2]) | ([0, 2]) | ([0, 2]) | Focuses on region around the minimum — ideal for Monte Carlo convergence plots. |
604
+ | **High-resolution contour visualization** | ([-1.5, 1.5]) | ([0, 2]) | ([0, 2]) | Best for plotting slices and seeing the curvature of the valley. |
605
+ | **Stress testing** | ([-2.048, 2.048]) | ([-2.048, 2.048]) | ([-2.048, 2.048]) | Tests the function's behavior over a wider range, useful for robustness checks. |
606
+ */
607
+ let tmpRosenbrockDataSourceObject = {};
608
+ let tmpRosenbrockDataDestinationObject = {};
609
+ let tmpRosenbrockParserResultsObject = {};
610
+ let tmpRosenbrockResult = _Parser.solve('RosenbrockResult = MONTECARLO SAMPLECOUNT 5000 VAR x PT x -3 PT x 3 VAR y PT y -1 PT y 5 VAR z PT z -1 PT z 5 : (1 - x)^2 + 100 * (y - x^2)^2 + (z - y)^2', tmpRosenbrockDataSourceObject, tmpRosenbrockParserResultsObject, tmpManifest, tmpRosenbrockDataDestinationObject);
611
+
612
+ Expect(tmpRosenbrockResult).to.exist;
613
+ Expect(tmpRosenbrockResult.Samples.length).to.equal(5000);
614
+
615
+ // Build a histogram of the results to see how many are near zero
616
+ let tmpHistogram = [];
617
+ for (let sampleIndex = 0; sampleIndex < tmpRosenbrockResult.Samples.length; sampleIndex++)
618
+ {
619
+ let tmpSample = tmpRosenbrockResult.Samples[sampleIndex];
620
+ let tmpValue = testFable.Math.roundPrecise(tmpSample, 0);
621
+ if (!tmpHistogram[tmpValue])
622
+ tmpHistogram[tmpValue] = 0;
623
+ tmpHistogram[tmpValue] = tmpHistogram[tmpValue] + 1;
624
+ // Log the output sample for visual inspection
625
+ testFable.log.info(`Rosenbrock Sample ${sampleIndex}: Value: ${tmpSample}, Rounded Value: ${tmpValue} --> Histogram Count: ${tmpHistogram[tmpValue]}`);
626
+ }
627
+ // Output the histogram for visual inspection
628
+ //testFable.ExpressionParser.Messaging.logMessage("Rosenbrock Histogram:");
629
+ let tmpHistogramKeys = Object.keys(tmpHistogram);
630
+ tmpHistogramKeys.sort((a, b) => parseFloat(a) - parseFloat(b));
631
+ for (let i = 0; i < tmpHistogramKeys.length; i++)
632
+ {
633
+ let key = tmpHistogramKeys[i];
634
+ testFable.log.info(`Value: ${key}, Count: ${tmpHistogram[key]}`);
635
+ }
636
+
535
637
  return fDone();
536
638
  }
537
639
  );
@@ -797,6 +899,234 @@ suite
797
899
  testFable.log.info('Series From Coefficients Result:', testFable.AppData.SeriesFromCoefficients);
798
900
  _Parser.solve('IntegratedSeries = SUM(FLATTEN(AppData.SeriesFromCoefficients))', testFable, testFable.AppData, false, testFable.AppData);
799
901
  testFable.log.info('Integrated Series Result:', testFable.AppData.IntegratedSeries);
902
+
903
+ const tmpRawData =
904
+ {
905
+ "StandardSelector": "English",
906
+ "MaxDryDensityTable": [
907
+ {
908
+ "MaxDryDensityTable": {
909
+ "f": "4.19",
910
+ "g": "125.7",
911
+ "j": "0.19",
912
+ "l": "109.1",
913
+ "NTest": "",
914
+ "a": "",
915
+ "b": "",
916
+ "d": "13.51",
917
+ "e": "9.32",
918
+ "h": "1.44",
919
+ "i": "1.25",
920
+ "k": "15.2"
921
+ },
922
+ "MaxDryDensityTablec": "10.00"
923
+ },
924
+ {
925
+ "MaxDryDensityTable": {
926
+ "f": "4.01",
927
+ "g": "120.3",
928
+ "j": "0.16",
929
+ "l": "106.5",
930
+ "NTest": "",
931
+ "a": "",
932
+ "b": "",
933
+ "d": "13.34",
934
+ "e": "9.33",
935
+ "h": "1.39",
936
+ "i": "1.23",
937
+ "k": "13"
938
+ },
939
+ "MaxDryDensityTablec": "11.00"
940
+ },
941
+ {
942
+ "MaxDryDensityTable": {
943
+ "f": "4.19",
944
+ "g": "125.7",
945
+ "j": "0.22",
946
+ "l": "106",
947
+ "NTest": "",
948
+ "a": "",
949
+ "b": "",
950
+ "d": "13.51",
951
+ "e": "9.32",
952
+ "h": "1.40",
953
+ "i": "1.18",
954
+ "k": "18.6"
955
+ },
956
+ "MaxDryDensityTablec": "12.00"
957
+ }
958
+ ],
959
+ "NuclearTable": [
960
+ {
961
+ "NuclearTable": {},
962
+ "NDD": "0",
963
+ "ADD": "0",
964
+ "NuclearPercentPR": "0"
965
+ }
966
+ ],
967
+ "Pulverization": [
968
+ {
969
+ "PTestNo": "1"
970
+ }
971
+ ],
972
+ "FMC": [
973
+ {
974
+ "MassOfWater": "0"
975
+ }
976
+ ],
977
+ "MetaTemplate": {},
978
+ "Header": {},
979
+ "SM": {
980
+ "SF": "0",
981
+ "SI": "0",
982
+ "SJ": "0"
983
+ },
984
+ "NM": {},
985
+ "Chart": {
986
+ "Slope": "1.18181818181818181818",
987
+ "Intercept": "91.136363636363636363664",
988
+ "MCZeroAtDD": "21.830887491264849755",
989
+ "ShiftToParallel": "3.230887491264849755",
990
+ "WetSideY2": "0.84615384615384615385",
991
+ "WetSideY1": "-36.84746008708272859272",
992
+ "DomainBegin": "13",
993
+ "DomainEnd": "18.6",
994
+ "WetSideY0": "-6240",
995
+ "OptimalMoistureContent": "16.27122843513086489567",
996
+ "DryCount": 16,
997
+ "WetCount": "16",
998
+ "TotalCount": "32",
999
+ "StartPlot": "12",
1000
+ "XValues": [
1001
+ "13.2",
1002
+ "13.4",
1003
+ "13.6",
1004
+ "13.8",
1005
+ "14",
1006
+ "14.2",
1007
+ "14.4",
1008
+ "14.6",
1009
+ "14.8",
1010
+ "15",
1011
+ "15.2",
1012
+ "15.4",
1013
+ "15.6",
1014
+ "15.8",
1015
+ "16",
1016
+ "16.2",
1017
+ "16.4",
1018
+ "16.6",
1019
+ "16.8",
1020
+ "17",
1021
+ "17.2",
1022
+ "17.4",
1023
+ "17.6",
1024
+ "17.8",
1025
+ "18",
1026
+ "18.2",
1027
+ "18.4",
1028
+ "18.6",
1029
+ "18.8",
1030
+ "19",
1031
+ "19.2",
1032
+ "19.4",
1033
+ ],
1034
+ "PrimaryRoot": "110.3659972415182948767",
1035
+ "WetCountIntermediate": "43",
1036
+ "ValueLimit": "28"
1037
+ },
1038
+ "Result": "",
1039
+ "FamilyOfCurvesZone": "0.99",
1040
+ "MaxDryDensityEnglish": "0",
1041
+ "MaxDryDensityMetric": "0",
1042
+ "DryX": "15.2",
1043
+ "DryY": "109.1",
1044
+ "AsIsX": "13",
1045
+ "AsIsY": "106.5",
1046
+ "WetX": "18.6",
1047
+ "WetY": "106",
1048
+ "AverageMaxDryDensity": "11",
1049
+ "OptimumMoistureOfTotalMaterial": "0.1",
1050
+ "om": "0.0",
1051
+ "pr": "0.0"
1052
+ };
1053
+
1054
+ const tmpBaseMoistures = tmpRawData.Chart.XValues;
1055
+ //TODO: compute this form above; 3 stages; dry, wet, combined
1056
+ const tmpCombinedDensities = [ '106.7363636','106.9727273','107.2090909','107.4454545','107.6818182','107.9181818',
1057
+ '108.1545455','108.3909091','108.6272727','108.8636364','109.1','109.3363636','109.5727273','109.8090909','110.0454545',
1058
+ '110.2818182','110.1152028','109.7279363','109.3433842','108.9615182','108.5823101','108.2057322','107.8317574',
1059
+ '107.4603587','107.0915096','106.7251839','106.3613559','106','105.6410912','105.2846046','104.9305159','104.5788009'
1060
+ ];
1061
+ const tmpMatrix = [];
1062
+ for (let i = 1; i <= 10; ++i)
1063
+ {
1064
+ const tmpPowArray = [];
1065
+ for (let j = 0; j < tmpBaseMoistures.length; ++j)
1066
+ {
1067
+ tmpPowArray.push(testFable.Math.powerPrecise(tmpBaseMoistures[j], i));
1068
+ }
1069
+ tmpMatrix.push(tmpPowArray);
1070
+ }
1071
+ testFable.AppData.FitToDensities = tmpCombinedDensities;
1072
+ testFable.AppData.MoistureMatrix = tmpMatrix;
1073
+ testFable.log.info('Fit To Densities:', testFable.AppData.FitToDensities);
1074
+ testFable.log.info('Moisture Matrix:', testFable.AppData.MoistureMatrix);
1075
+ _Parser.solve('LinearRegressionHand = LINEST(AppData.MoistureMatrix, AppData.FitToDensities)', testFable, testFable.AppData, false, testFable.AppData);
1076
+ testFable.log.info('Density Regression Coefficients:', testFable.AppData.LinearRegressionHand);
1077
+
1078
+ const tmpSolverInstructions =
1079
+ [
1080
+ 'ChartDomainBegin = MaxDryDensityTable[0].MaxDryDensityTable.k - 2',
1081
+ 'ChartDomainEnd = ChartDomainBegin + (Chart.TotalCount - 1) * 0.2',
1082
+ 'XValues = SERIES FROM ChartDomainBegin TO ChartDomainEnd STEP 0.2 : n + 0',
1083
+ 'DryCount = MATCH(Chart.OptimalMoistureContent, XValues)',
1084
+ 'WetCountIntermediate = 60 - DryCount',
1085
+ 'WetCount = IF(DryCount, "<=", 30, DryCount, WetCountIntermediate)',
1086
+ 'TotalCount = DryCount + WetCount',
1087
+ 'StartPlot = DryCount - 5',
1088
+ 'ChartValueLimit = Chart.XValues.length - 1',
1089
+ 'DryValues = MAP VAR x FROM Chart.XValues : x * Chart.Slope + Chart.Intercept',
1090
+ 'WetValues = MAP VAR x FROM Chart.XValues : 6240 / (x + 100 / 2.7 + Chart.ShiftToParallel)',
1091
+ 'CombinedValues = MAP VAR dry FROM DryValues VAR wet FROM WetValues VAR x from XValues : IF(x, "LTE", Chart.OptimalMoistureContent, dry, wet)',
1092
+ 'FilteredXValues = MAP VAR x FROM XValues : IF(ABS(Chart.OptimalMoistureContent - x), "LT", 1, x, 0)',
1093
+ 'XValueMatrix = createarrayfromabsolutevalues(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)', // Create Placeholders
1094
+ 'XValueMatrix[0] = MAP VAR x FROM XValues : x + 0',
1095
+ 'XValueMatrix[1] = MAP VAR x FROM XValues : x^2',
1096
+ 'XValueMatrix[2] = MAP VAR x FROM XValues : x^3',
1097
+ 'XValueMatrix[3] = MAP VAR x FROM XValues : x^4',
1098
+ 'XValueMatrix[4] = MAP VAR x FROM XValues : x^5',
1099
+ 'XValueMatrix[5] = MAP VAR x FROM XValues : x^6',
1100
+ 'XValueMatrix[6] = MAP VAR x FROM XValues : x^7',
1101
+ 'XValueMatrix[7] = MAP VAR x FROM XValues : x^8',
1102
+ 'XValueMatrix[8] = MAP VAR x FROM XValues : x^9',
1103
+ 'XValueMatrix[9] = MAP VAR x FROM XValues : x^10',
1104
+ 'LinearRegression = LINEST(XValueMatrix, CombinedValues)',
1105
+ 'FilteredXValueVectors = MatrixTranspose(XValueMatrix)',
1106
+ 'FittedDensities = MAP VAR x FROM XValues VAR vector FROM FilteredXValueVectors : PREDICT(LinearRegression, vector)',
1107
+ 'FilteredDensities = MAP VAR x FROM FilteredXValues VAR prediction FROM FittedDensities : IF(x, "==", 0, 0, prediction)',
1108
+ ];
1109
+ const tmpResultsObject = {};
1110
+ for (const tmpInstruction of tmpSolverInstructions)
1111
+ {
1112
+ _Parser.solve(tmpInstruction, tmpRawData, tmpResultsObject, false, tmpRawData);
1113
+ testFable.log.info(`After instruction: ${tmpInstruction}`, tmpRawData[tmpInstruction.split('=')[0].trim()]);
1114
+ }
1115
+
1116
+ Expect(tmpRawData.FilteredDensities[10]).to.equal('0');
1117
+ Expect(Number(tmpRawData.FilteredDensities[11])).to.be.closeTo(109.3, 0.1);
1118
+ Expect(Number(tmpRawData.FilteredDensities[20])).to.be.closeTo(108.6, 0.1);
1119
+ Expect(tmpRawData.FilteredDensities[21]).to.equal('0');
1120
+
1121
+ /*
1122
+ testFable.log.info('linest payload (hand):', { MoistureMatrix: testFable.AppData.MoistureMatrix, FitToDensities: testFable.AppData.FitToDensities });
1123
+ testFable.log.info('linest payload (solver):', { XValueMatrix: tmpRawData.XValueMatrix, CombinedValues: tmpRawData.CombinedValues });
1124
+
1125
+ testFable.log.info('coefficients (hand):', testFable.AppData.LinearRegressionHand);
1126
+ testFable.log.info('coefficients (solver):', tmpRawData.LinearRegression);
1127
+
1128
+ testFable.log.info('filtered densities (solver):', tmpRawData.FilteredDensities);
1129
+ */
800
1130
  }
801
1131
  );
802
1132
  }
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