mathjson-solver 1.15.0__tar.gz → 1.16.0__tar.gz

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.
Files changed (29) hide show
  1. {mathjson_solver-1.15.0/src/mathjson_solver.egg-info → mathjson_solver-1.16.0}/PKG-INFO +1 -1
  2. {mathjson_solver-1.15.0 → mathjson_solver-1.16.0}/setup.cfg +1 -1
  3. {mathjson_solver-1.15.0 → mathjson_solver-1.16.0}/setup.py +1 -1
  4. {mathjson_solver-1.15.0 → mathjson_solver-1.16.0}/src/mathjson_solver/__main__.py +327 -0
  5. {mathjson_solver-1.15.0 → mathjson_solver-1.16.0/src/mathjson_solver.egg-info}/PKG-INFO +1 -1
  6. mathjson_solver-1.16.0/src/mathjson_solver.egg-info/SOURCES.txt +26 -0
  7. mathjson_solver-1.16.0/tests/test_CumulativeProduct.py +18 -0
  8. mathjson_solver-1.16.0/tests/test_Slice.py +37 -0
  9. mathjson_solver-1.16.0/tests/test_TrapezoidalIntegratel.py +218 -0
  10. mathjson_solver-1.16.0/tests/test_atindex.py +27 -0
  11. mathjson_solver-1.16.0/tests/test_equal.py +50 -0
  12. mathjson_solver-1.16.0/tests/test_exceptions.py +83 -0
  13. mathjson_solver-1.16.0/tests/test_extract_variables.py +45 -0
  14. mathjson_solver-1.16.0/tests/test_findinterval.py +155 -0
  15. mathjson_solver-1.16.0/tests/test_gail_model.py +952 -0
  16. mathjson_solver-1.16.0/tests/test_interpolate.py +80 -0
  17. mathjson_solver-1.16.0/tests/test_range.py +38 -0
  18. mathjson_solver-1.16.0/tests/test_scalars.py +48 -0
  19. mathjson_solver-1.16.0/tests/test_sets.py +79 -0
  20. mathjson_solver-1.16.0/tests/test_simple_cases.py +44 -0
  21. mathjson_solver-1.16.0/tests/test_with_pytest.py +338 -0
  22. mathjson_solver-1.15.0/src/mathjson_solver.egg-info/SOURCES.txt +0 -12
  23. mathjson_solver-1.15.0/tests/test_with_pytest.py +0 -752
  24. {mathjson_solver-1.15.0 → mathjson_solver-1.16.0}/LICENSE +0 -0
  25. {mathjson_solver-1.15.0 → mathjson_solver-1.16.0}/README.md +0 -0
  26. {mathjson_solver-1.15.0 → mathjson_solver-1.16.0}/pyproject.toml +0 -0
  27. {mathjson_solver-1.15.0 → mathjson_solver-1.16.0}/src/mathjson_solver/__init__.py +0 -0
  28. {mathjson_solver-1.15.0 → mathjson_solver-1.16.0}/src/mathjson_solver.egg-info/dependency_links.txt +0 -0
  29. {mathjson_solver-1.15.0 → mathjson_solver-1.16.0}/src/mathjson_solver.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mathjson-solver
3
- Version: 1.15.0
3
+ Version: 1.16.0
4
4
  Summary: Utilities for MathJSON evaluation
5
5
  Home-page: https://github.com/LongenesisLtd/mathjson-solver
6
6
  Author: Martins Mednis
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = mathjson-solver
3
- version = 1.15.0
3
+ version = 1.16.0
4
4
  author = Martins Mednis
5
5
  author_email = mrt@mednis.info
6
6
  description = Utilities for MathJSON evaluation
@@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
5
5
 
6
6
  setuptools.setup(
7
7
  name="mathjson-solver",
8
- version="1.15.0",
8
+ version="1.16.0",
9
9
  author="Martins Mednis",
10
10
  author_email="mrt@mednis.info",
11
11
  description="Utilities for MathJSON evaluation",
@@ -19,6 +19,107 @@ except ImportError:
19
19
  NoneType = type(None)
20
20
 
21
21
 
22
+ # def find_interpolation_bounds(
23
+ # l: list, target: int | float
24
+ # ) -> Union[Union[int, float], tuple[Union[int, float], Union[int, float]]]:
25
+ # for i, x in enumerate(l):
26
+ # if i == 0:
27
+ # continue
28
+ # if l[i - 1] <= target and target <= l[i]:
29
+ # if target == l[i - 1] or target == l[i]:
30
+ # return target
31
+ # else:
32
+ # return l[i - 1], l[i]
33
+ # else:
34
+ # raise ValueError("Target value is outside interpolation range.")
35
+
36
+
37
+ def _MultiplyByScalar(l: list[numbers.Real], a: numbers.Real) -> list[numbers.Real]:
38
+ return [x * a for x in l]
39
+
40
+
41
+ def _MultiplyByArray(
42
+ l1: list[numbers.Real], l2: list[numbers.Real]
43
+ ) -> list[numbers.Real]:
44
+ return [a * b for a, b in zip(l1, l2)]
45
+
46
+
47
+ def _AddScalar(l: list[numbers.Real], a: numbers.Real) -> list[numbers.Real]:
48
+ return [x + a for x in l]
49
+
50
+
51
+ def _SubtractScalar(l: list[numbers.Real], a: numbers.Real) -> list[numbers.Real]:
52
+ return [x - a for x in l]
53
+
54
+
55
+ def _AddArray(l1: list[numbers.Real], l2: list[numbers.Real]) -> list[numbers.Real]:
56
+ return [a + b for a, b in zip(l1, l2)]
57
+
58
+
59
+ def _SubtractArray(
60
+ l1: list[numbers.Real], l2: list[numbers.Real]
61
+ ) -> list[numbers.Real]:
62
+ return [a - b for a, b in zip(l1, l2)]
63
+
64
+
65
+ def _CumulativeProduct(l: list) -> list:
66
+ res = []
67
+ for i, x in enumerate(l):
68
+ if i == 0:
69
+ res.append(x)
70
+ else:
71
+ res.append(reduce(lambda a, b: a * b, l[: i + 1]))
72
+ return res
73
+
74
+
75
+ def find_interpolation_bounds_indexes(
76
+ l: list, target: int | float
77
+ ) -> Union[Union[int, float], tuple[Union[int, float], Union[int, float]]]:
78
+ for i, x in enumerate(l):
79
+ if i == 0:
80
+ continue
81
+ if l[i - 1] <= target and target <= l[i]:
82
+ if target == l[i - 1]:
83
+ return i - 1
84
+ elif target == l[i]:
85
+ return i
86
+ else:
87
+ return i - 1, i
88
+ else:
89
+ raise ValueError("Target value is outside interpolation range.")
90
+
91
+
92
+ def find_interpolation_bounds_2indexes(
93
+ l: list, target: int | float
94
+ ) -> Union[Union[int, float], tuple[Union[int, float], Union[int, float]]]:
95
+ for i, x in enumerate(l):
96
+ if i == len(l) - 1:
97
+ return i - 1, i
98
+ if l[i] <= target and target < l[i + 1]:
99
+ return i, i + 1
100
+ else:
101
+ raise ValueError("Target value is outside interpolation range.")
102
+
103
+
104
+ def linear_interpolate(x_array, y_array, target_x):
105
+ # Find the interval where target_x falls
106
+ # Handle edge cases (target_x outside range)
107
+ # Apply: y = y1 + (y2 - y1) * (target_x - x1) / (x2 - x1)
108
+
109
+ # first check if both arrays are the same length
110
+ if len(x_array) != len(y_array) or len(x_array) < 2:
111
+ raise ValueError(
112
+ "Both arrays need to be the same length and with at least 2 elements."
113
+ )
114
+ bounds_indexes = find_interpolation_bounds_indexes(x_array, target_x)
115
+ if isinstance(bounds_indexes, tuple):
116
+ x1, x2 = x_array[bounds_indexes[0]], x_array[bounds_indexes[1]]
117
+ y1, y2 = y_array[bounds_indexes[0]], y_array[bounds_indexes[1]]
118
+ return y1 + (y2 - y1) * (target_x - x1) / (x2 - x1)
119
+ else:
120
+ return y_array[bounds_indexes]
121
+
122
+
22
123
  class MathJSONException(Exception):
23
124
  """Exception for MathJSON processing issues"""
24
125
 
@@ -578,6 +679,204 @@ def create_mathjson_solver(solver_parameters):
578
679
  return 0
579
680
  # return f(function_expression, c) function_name(*parameters)
580
681
 
682
+ def MultiplyByScalar(s):
683
+ """
684
+ ["MultiplyByScalar", array, scalar]
685
+ The `array` must be an array of numeric values.
686
+ The `scalar` is the number to multiply each element by.
687
+ """
688
+ array = f(s[1], c)
689
+ scalar = f(s[2], c)
690
+ if not (isinstance(array, list) and array[0] == "Array"):
691
+ raise ValueError("Parameter 1 must be an array.")
692
+ array = [f(x, c) for x in array[1:]]
693
+ return ["Array"] + _MultiplyByScalar(array, scalar)
694
+
695
+ def MultiplyByArray(s):
696
+ """
697
+ ["MultiplyByArray", array1, array2]
698
+ The `array1` and `array2` must be arrays of the same length.
699
+ """
700
+ array1 = f(s[1], c)
701
+ array2 = f(s[2], c)
702
+ if not (isinstance(array1, list) and array1[0] == "Array"):
703
+ raise ValueError("Parameter 1 must be an array.")
704
+ if not (isinstance(array2, list) and array2[0] == "Array"):
705
+ raise ValueError("Parameter 2 must be an array.")
706
+ array1 = [f(x, c) for x in array1[1:]]
707
+ array2 = [f(x, c) for x in array2[1:]]
708
+ if len(array1) != len(array2):
709
+ raise ValueError("Both arrays must be of the same length.")
710
+ return ["Array"] + _MultiplyByArray(array1, array2)
711
+
712
+ def AddScalar(s):
713
+ """
714
+ ["AddScalar", array, scalar]
715
+ The `array` must be an array of numeric values.
716
+ The `scalar` is the number to add to each element.
717
+ """
718
+ array = f(s[1], c)
719
+ scalar = f(s[2], c)
720
+ if not (isinstance(array, list) and array[0] == "Array"):
721
+ raise ValueError("Parameter 1 must be an array.")
722
+ array = [f(x, c) for x in array[1:]]
723
+ return ["Array"] + _AddScalar(array, scalar)
724
+
725
+ def SubtractScalar(s):
726
+ """
727
+ ["SubtractScalar", array, scalar]
728
+ The `array` must be an array of numeric values.
729
+ The `scalar` is the number to subtract from each element.
730
+ """
731
+ array = f(s[1], c)
732
+ scalar = f(s[2], c)
733
+ if not (isinstance(array, list) and array[0] == "Array"):
734
+ raise ValueError("Parameter 1 must be an array.")
735
+ array = [f(x, c) for x in array[1:]]
736
+ return ["Array"] + _SubtractScalar(array, scalar)
737
+
738
+ def AddArray(s):
739
+ """
740
+ ["AddArray", array1, array2]
741
+ The `array1` and `array2` must be arrays of the same length.
742
+ """
743
+ array1 = f(s[1], c)
744
+ array2 = f(s[2], c)
745
+ if not (isinstance(array1, list) and array1[0] == "Array"):
746
+ raise ValueError("Parameter 1 must be an array.")
747
+ if not (isinstance(array2, list) and array2[0] == "Array"):
748
+ raise ValueError("Parameter 2 must be an array.")
749
+ array1 = [f(x, c) for x in array1[1:]]
750
+ array2 = [f(x, c) for x in array2[1:]]
751
+ if len(array1) != len(array2):
752
+ raise ValueError("Both arrays must be of the same length.")
753
+ return ["Array"] + _AddArray(array1, array2)
754
+
755
+ def SubtractArray(s):
756
+ """
757
+ ["SubtractArray", array1, array2]
758
+ The `array1` and `array2` must be arrays of the same length.
759
+ """
760
+ array1 = f(s[1], c)
761
+ array2 = f(s[2], c)
762
+ if not (isinstance(array1, list) and array1[0] == "Array"):
763
+ raise ValueError("Parameter 1 must be an array.")
764
+ if not (isinstance(array2, list) and array2[0] == "Array"):
765
+ raise ValueError("Parameter 2 must be an array.")
766
+ array1 = [f(x, c) for x in array1[1:]]
767
+ array2 = [f(x, c) for x in array2[1:]]
768
+ if len(array1) != len(array2):
769
+ raise ValueError("Both arrays must be of the same length.")
770
+ return ["Array"] + _SubtractArray(array1, array2)
771
+
772
+ def GenerateRange(s):
773
+ """
774
+ ["GenerateRange", end]
775
+ or
776
+ ["GenerateRange", start, end, step]
777
+ The `start`, `end`, and `step` are numeric values.
778
+ """
779
+ if len(s) == 2:
780
+ end = f(s[1], c)
781
+ start = 0
782
+ step = 1
783
+ elif len(s) == 4:
784
+ start = f(s[1], c)
785
+ end = f(s[2], c)
786
+ step = f(s[3], c)
787
+ else:
788
+ raise ValueError(
789
+ "GenerateRange requires either 1 or 3 parameters (end or start, end, step)."
790
+ )
791
+ if step == 0:
792
+ raise ValueError("Step cannot be zero.")
793
+ if (start < end and step < 0) or (start > end and step > 0):
794
+ raise ValueError("Step direction is incorrect for the given range.")
795
+ result = ["Array"]
796
+ if start < end:
797
+ current = start
798
+ while current < end:
799
+ result.append(current)
800
+ current += step
801
+ else:
802
+ current = start
803
+ while current > end:
804
+ result.append(current)
805
+ current += step
806
+ return result
807
+
808
+ def AtIndex(s):
809
+ """
810
+ ["AtIndex", array, index]
811
+ The `array` must be an array of values.
812
+ The `index` is the index of the element to retrieve.
813
+ """
814
+ array = f(s[1], c)
815
+ index = f(s[2], c)
816
+ if not (isinstance(array, list) and array[0] == "Array"):
817
+ raise ValueError("Parameter 1 must be an array.")
818
+ # array = [f(x, c) for x in array[1:]]
819
+ # return array[index]
820
+ array = [x for x in array[1:]]
821
+ return f(array[index], c)
822
+
823
+ def Slice(s):
824
+ """
825
+ ["Slice", array, start, end]
826
+ The `array` must be an array of values.
827
+ The `start` and `end` are the slice indices.
828
+ """
829
+ array = f(s[1], c)
830
+ start = f(s[2], c)
831
+ end = f(s[3], c)
832
+ if not (isinstance(array, list) and array[0] == "Array"):
833
+ raise ValueError("Parameter 1 must be an array.")
834
+ array = [f(x, c) for x in array[1:]]
835
+ return ["Array"] + array[start:end]
836
+
837
+ def CumulativeProduct(s):
838
+ """
839
+ ["CumulativeProduct", array]
840
+ The `array` must be an array of numeric values.
841
+ """
842
+ array = f(s[1], c)
843
+ if not (isinstance(array, list) and array[0] == "Array"):
844
+ raise ValueError("Parameter 1 must be an array.")
845
+ array = [f(x, c) for x in array[1:]]
846
+ return ["Array"] + _CumulativeProduct(array)
847
+ # return _CumulativeProduct(array)
848
+
849
+ def Interp(s):
850
+ """
851
+ ["Interp", x_array, y_array, target_x]
852
+ The `x_array` and `y_array` must be arrays of the same length.
853
+ The `target_x` is the x value to interpolate for.
854
+ """
855
+ x_array = f(s[1], c)
856
+ y_array = f(s[2], c)
857
+ target_x = f(s[3], c)
858
+ if not (isinstance(x_array, list) and x_array[0] == "Array"):
859
+ raise ValueError("Parameter 1 must be an array.")
860
+ if not (isinstance(y_array, list) and y_array[0] == "Array"):
861
+ raise ValueError("Parameter 2 must be an array.")
862
+ x_array = [f(x, c) for x in x_array[1:]]
863
+ y_array = [f(y, c) for y in y_array[1:]]
864
+ return linear_interpolate(x_array, y_array, target_x)
865
+
866
+ def FindIntervalIndex(s):
867
+ """
868
+ ["FindIntervalIndex", array, target_value]
869
+ The `array` must be an array of numeric values.
870
+ The `target_value` is the value to find the interval index for.
871
+ """
872
+ array = f(s[1], c)
873
+ target_value = f(s[2], c)
874
+ if not (isinstance(array, list) and array[0] == "Array"):
875
+ raise ValueError("Parameter 1 must be an array.")
876
+ array = [f(x, c) for x in array[1:]]
877
+ zz = find_interpolation_bounds_2indexes(array, target_value)
878
+ return zz[0]
879
+
581
880
  def TrapezoidalIntegrate(s):
582
881
  """
583
882
  ["TrapezoidalIntegrate", function_expression, start, end, n, variable]
@@ -690,6 +989,18 @@ def create_mathjson_solver(solver_parameters):
690
989
  "TimeDeltaDays": TimeDeltaDays,
691
990
  "Function": Function,
692
991
  "Variable": Variable,
992
+ "MultiplyByScalar": MultiplyByScalar,
993
+ "MultiplyByArray": MultiplyByArray,
994
+ "AddScalar": AddScalar,
995
+ "SubtractScalar": SubtractScalar,
996
+ "AddArray": AddArray,
997
+ "SubtractArray": SubtractArray,
998
+ "GenerateRange": GenerateRange,
999
+ "AtIndex": AtIndex,
1000
+ "Slice": Slice,
1001
+ "CumulativeProduct": CumulativeProduct,
1002
+ "Interp": Interp,
1003
+ "FindIntervalIndex": FindIntervalIndex,
693
1004
  "TrapezoidalIntegrate": TrapezoidalIntegrate,
694
1005
  "Sin": Sin,
695
1006
  "Cos": Cos,
@@ -699,6 +1010,9 @@ def create_mathjson_solver(solver_parameters):
699
1010
  "Arctan": Arctan,
700
1011
  "Pi": Pi,
701
1012
  }
1013
+ if not s:
1014
+ # Empty equation given - []
1015
+ return None
702
1016
  if s[0] in constructs:
703
1017
  try:
704
1018
  return constructs[s[0]](s)
@@ -795,6 +1109,19 @@ def extract_variables(s: Union[list, int, float, str], li: set, ignore_list: set
795
1109
  "TimeDeltaDays",
796
1110
  "Function",
797
1111
  "Variable",
1112
+ "MultiplyByScalar",
1113
+ "MultiplyByArray",
1114
+ "AddScalar",
1115
+ "SubtractScalar",
1116
+ "AddArray",
1117
+ "SubtractArray",
1118
+ "GenerateRange",
1119
+ "AtIndex",
1120
+ "Slice",
1121
+ "CumulativeProduct",
1122
+ "Interp",
1123
+ "FindIntervalIndex",
1124
+ "TrapezoidalIntegrate",
798
1125
  "TrapezoidalIntegrate",
799
1126
  "Sin",
800
1127
  "Cos",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mathjson-solver
3
- Version: 1.15.0
3
+ Version: 1.16.0
4
4
  Summary: Utilities for MathJSON evaluation
5
5
  Home-page: https://github.com/LongenesisLtd/mathjson-solver
6
6
  Author: Martins Mednis
@@ -0,0 +1,26 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ setup.cfg
5
+ setup.py
6
+ src/mathjson_solver/__init__.py
7
+ src/mathjson_solver/__main__.py
8
+ src/mathjson_solver.egg-info/PKG-INFO
9
+ src/mathjson_solver.egg-info/SOURCES.txt
10
+ src/mathjson_solver.egg-info/dependency_links.txt
11
+ src/mathjson_solver.egg-info/top_level.txt
12
+ tests/test_CumulativeProduct.py
13
+ tests/test_Slice.py
14
+ tests/test_TrapezoidalIntegratel.py
15
+ tests/test_atindex.py
16
+ tests/test_equal.py
17
+ tests/test_exceptions.py
18
+ tests/test_extract_variables.py
19
+ tests/test_findinterval.py
20
+ tests/test_gail_model.py
21
+ tests/test_interpolate.py
22
+ tests/test_range.py
23
+ tests/test_scalars.py
24
+ tests/test_sets.py
25
+ tests/test_simple_cases.py
26
+ tests/test_with_pytest.py
@@ -0,0 +1,18 @@
1
+ import sys
2
+ import os
3
+ import pytest
4
+
5
+ sys.path.append(os.path.join(os.path.dirname(__file__), "../src/"))
6
+
7
+ from mathjson_solver import create_solver
8
+
9
+
10
+ def test_CumulativeProduct1():
11
+ parameters = {"test_array": ["Array", 2, 3, 4, 5]}
12
+
13
+ expression = ["CumulativeProduct", "test_array"]
14
+
15
+ expected_result = ["Array", 2, 6, 24, 120]
16
+ solver = create_solver(parameters)
17
+ # assert solver(expression) == expected_result
18
+ assert solver(expression) == expected_result
@@ -0,0 +1,37 @@
1
+ import sys
2
+ import os
3
+ import pytest
4
+
5
+ sys.path.append(os.path.join(os.path.dirname(__file__), "../src/"))
6
+
7
+ from mathjson_solver import create_solver
8
+
9
+
10
+ @pytest.mark.parametrize(
11
+ "parameters, expression, expected_result",
12
+ [
13
+ (
14
+ {"test_array": ["Array", 10, 20, 30, 40, 50, 60]},
15
+ ["Slice", "test_array", 2, 4],
16
+ ["Array", 30, 40],
17
+ ),
18
+ (
19
+ {"test_array": ["Array", 10, 20, 30, 40, 50, 60]},
20
+ ["Slice", "test_array", 2, 5],
21
+ ["Array", 30, 40, 50],
22
+ ),
23
+ (
24
+ {"test_array": ["Array", 10, 20, 30, 40, 50, 60]},
25
+ ["Length", ["Slice", "test_array", 2, 5]],
26
+ 3,
27
+ ),
28
+ (
29
+ {"test_array": ["Array", 10, 20, 30, 40, 50, 60]},
30
+ ["Sum", ["Slice", "test_array", 2, 5]],
31
+ 120,
32
+ ),
33
+ ],
34
+ )
35
+ def test_slice1(parameters, expression, expected_result):
36
+ solver = create_solver(parameters)
37
+ assert solver(expression) == expected_result