mathjson-solver 1.12.1__tar.gz → 1.14.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.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: mathjson-solver
3
- Version: 1.12.1
3
+ Version: 1.14.0
4
4
  Summary: Utilities for MathJSON evaluation
5
5
  Home-page: https://github.com/LongenesisLtd/mathjson-solver
6
6
  Author: Martins Mednis
@@ -18,6 +18,7 @@ Dynamic: classifier
18
18
  Dynamic: description
19
19
  Dynamic: description-content-type
20
20
  Dynamic: home-page
21
+ Dynamic: license-file
21
22
  Dynamic: project-url
22
23
  Dynamic: requires-python
23
24
  Dynamic: summary
@@ -26,7 +27,7 @@ Dynamic: summary
26
27
 
27
28
  [![PyPI](https://img.shields.io/pypi/v/mathjson-solver.svg)](https://pypi.org/project/mathjson-solver/)
28
29
  [![PyPI Downloads](https://static.pepy.tech/badge/mathjson-solver/month)](https://pepy.tech/projects/mathjson-solver)
29
- ![Coverage](https://img.shields.io/badge/coverage-93-green)
30
+ ![Coverage](https://img.shields.io/badge/coverage-91-green)
30
31
 
31
32
  _MathJSON Solver_ is a Python module to numerically evaluate MathJSON expressions, like `["Add", 1, 2]`. It is developed by [Longenesis](https://longenesis.com/team) to enable numerical evaluation of user provided mathematical expressions in Longenesis digital health products. Its development was inspired by [CortexJS](https://cortexjs.io/compute-engine/) Compute Engine.
32
33
 
@@ -73,11 +74,3 @@ Make sure you have `pytest` installed. Then `cd` into project directory and run:
73
74
  ```bash
74
75
  pytest
75
76
  ```
76
-
77
- ## Contributing Code
78
-
79
- We welcome your contributions in the form of pull requests.
80
-
81
- 1. Fork the repo;
82
- 2. Make improvements;
83
- 3. Make a pull request to share your improvements with the community and to include it into official release.
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![PyPI](https://img.shields.io/pypi/v/mathjson-solver.svg)](https://pypi.org/project/mathjson-solver/)
4
4
  [![PyPI Downloads](https://static.pepy.tech/badge/mathjson-solver/month)](https://pepy.tech/projects/mathjson-solver)
5
- ![Coverage](https://img.shields.io/badge/coverage-93-green)
5
+ ![Coverage](https://img.shields.io/badge/coverage-91-green)
6
6
 
7
7
  _MathJSON Solver_ is a Python module to numerically evaluate MathJSON expressions, like `["Add", 1, 2]`. It is developed by [Longenesis](https://longenesis.com/team) to enable numerical evaluation of user provided mathematical expressions in Longenesis digital health products. Its development was inspired by [CortexJS](https://cortexjs.io/compute-engine/) Compute Engine.
8
8
 
@@ -49,11 +49,3 @@ Make sure you have `pytest` installed. Then `cd` into project directory and run:
49
49
  ```bash
50
50
  pytest
51
51
  ```
52
-
53
- ## Contributing Code
54
-
55
- We welcome your contributions in the form of pull requests.
56
-
57
- 1. Fork the repo;
58
- 2. Make improvements;
59
- 3. Make a pull request to share your improvements with the community and to include it into official release.
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = mathjson-solver
3
- version = 1.12.1
3
+ version = 1.14.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.12.1",
8
+ version="1.14.0",
9
9
  author="Martins Mednis",
10
10
  author_email="mrt@mednis.info",
11
11
  description="Utilities for MathJSON evaluation",
@@ -1,5 +1,5 @@
1
1
  import numbers
2
- from typing import Union
2
+ from typing import Union, Any
3
3
  from functools import reduce
4
4
  import math
5
5
  from copy import deepcopy
@@ -10,6 +10,7 @@ import datetime
10
10
 
11
11
  NoneType = type(None)
12
12
 
13
+
13
14
  class MathJSONException(Exception):
14
15
  """Exception for MathJSON processing issues"""
15
16
 
@@ -45,6 +46,8 @@ def is_numeric(x):
45
46
  float(x)
46
47
  except ValueError:
47
48
  return False
49
+ except TypeError:
50
+ return False
48
51
  else:
49
52
  return True
50
53
 
@@ -140,6 +143,16 @@ def comparison_safe_converter(x):
140
143
  return x
141
144
 
142
145
 
146
+ def comparison_safe_converter_for_pairs(
147
+ v1, v2
148
+ ) -> (Union[str, float], Union[str, float]):
149
+ if is_numeric(v1):
150
+ v1 = float(v1)
151
+ if is_numeric(v2):
152
+ v2 = float(v2)
153
+ return v1, v2
154
+
155
+
143
156
  def create_mathjson_solver(solver_parameters):
144
157
  def f(s, *args):
145
158
  if args:
@@ -159,10 +172,15 @@ def create_mathjson_solver(solver_parameters):
159
172
  tmp = 0
160
173
  for i, x in enumerate(s[1:]):
161
174
  res = f(x, c)
175
+ if is_numeric(res):
176
+ res = float(res)
162
177
  if i == 0:
163
178
  tmp = res
164
179
  else:
165
- tmp = tmp + res
180
+ try:
181
+ tmp = tmp + res
182
+ except TypeError:
183
+ pass
166
184
 
167
185
  # if isinstance(res, list):
168
186
  # l_res.append(sum([xx for xx in res[1:]]))
@@ -179,15 +197,25 @@ def create_mathjson_solver(solver_parameters):
179
197
  # tmp = tmp + x
180
198
  # return tmp
181
199
 
200
+ # def Sum(s):
201
+ # l_res = []
202
+ # for x in s[1:]:
203
+ # res = f(x, c)
204
+ # if isinstance(res, list):
205
+ # l_res.append(sum([xx for xx in res[1:]]))
206
+ # else:
207
+ # l_res.append(res)
208
+ # return sum(l_res)
209
+
182
210
  def Sum(s):
183
- l_res = []
211
+ l_res = ["Array"]
184
212
  for x in s[1:]:
185
213
  res = f(x, c)
186
214
  if isinstance(res, list):
187
- l_res.append(sum([xx for xx in res[1:]]))
215
+ l_res.append(Add(["Array"] + [xx for xx in res[1:]]))
188
216
  else:
189
217
  l_res.append(res)
190
- return sum(l_res)
218
+ return Add(l_res)
191
219
 
192
220
  def Max(s):
193
221
  if isinstance(s[1], str):
@@ -221,7 +249,6 @@ def create_mathjson_solver(solver_parameters):
221
249
 
222
250
  def Length(s):
223
251
  if isinstance(s[1], str):
224
-
225
252
  return len([x for x in f(s[1], c)][1:])
226
253
  else:
227
254
  return len([x for x in s[1][1:]])
@@ -253,6 +280,20 @@ def create_mathjson_solver(solver_parameters):
253
280
  raise ValueError(
254
281
  "Case of 'Switch' should have exactly two parameters"
255
282
  )
283
+ if comparison_safe_converter(
284
+ expression
285
+ ) == comparison_safe_converter(f(x[0], c)):
286
+ return f(x[1], c)
287
+ else:
288
+ return f(s[2], c)
289
+
290
+ def StrictSwitch(s):
291
+ expression = f(s[1], c)
292
+ for x in s[3:]:
293
+ if len(x) != 2:
294
+ raise ValueError(
295
+ "Case of 'StrictSwitch' should have exactly two parameters"
296
+ )
256
297
  if expression == f(x[0], c):
257
298
  return f(x[1], c)
258
299
  else:
@@ -337,6 +378,24 @@ def create_mathjson_solver(solver_parameters):
337
378
  return not f(s[1])
338
379
 
339
380
  def Map(s):
381
+ """
382
+ ["Map", list, function, more parameters]
383
+ The `function` must accept at least one parameter. That is for the current loop element.
384
+ The `more parameters` are for any additional parameters that function might have.
385
+ """
386
+ z = f(s[1], c)
387
+ if isinstance(z, list):
388
+ retlist = ["Array"]
389
+ for x in z[1:]:
390
+ try:
391
+ the_function_name = s[2][0]
392
+ ss = [the_function_name, x] + s[3:]
393
+ retlist.append(f(ss, c))
394
+ except MathJSONException:
395
+ retlist.append(x)
396
+ return retlist
397
+
398
+ def StrictMap(s):
340
399
  """
341
400
  ["Map", list, function, more parameters]
342
401
  The `function` must accept at least one parameter. That is for the current loop element.
@@ -351,6 +410,22 @@ def create_mathjson_solver(solver_parameters):
351
410
  retlist.append(f(ss, c))
352
411
  return retlist
353
412
 
413
+ def Filter(s):
414
+ """
415
+ ["Filter", list, function, more parameters]
416
+ The `function` must accept at least one parameter. That is for the current loop element.
417
+ The `more parameters` are for any additional parameters that function might have.
418
+ """
419
+ z = f(s[1], c)
420
+ if isinstance(z, list):
421
+ retlist = ["Array"]
422
+ for x in z[1:]:
423
+ the_function_name = s[2][0]
424
+ ss = [the_function_name, x] + s[3:]
425
+ if f(ss, c):
426
+ retlist.append(x)
427
+ return retlist
428
+
354
429
  def HasMatchingSublist(s):
355
430
  """
356
431
  ["HasMatchingSublist", list, required_match_count, position, contiguous, function, more parameters]
@@ -423,6 +498,34 @@ def create_mathjson_solver(solver_parameters):
423
498
  # Todo: This does not work
424
499
  return s[1] in c.keys()
425
500
 
501
+ def Greater(s):
502
+ v1, v2 = comparison_safe_converter_for_pairs(f(s[1], c), f(s[2], c))
503
+ try:
504
+ return v1 > v2
505
+ except TypeError:
506
+ return False
507
+
508
+ def GreaterEqual(s):
509
+ v1, v2 = comparison_safe_converter_for_pairs(f(s[1], c), f(s[2], c))
510
+ try:
511
+ return v1 >= v2
512
+ except TypeError:
513
+ return False
514
+
515
+ def Less(s):
516
+ v1, v2 = comparison_safe_converter_for_pairs(f(s[1], c), f(s[2], c))
517
+ try:
518
+ return v1 < v2
519
+ except TypeError:
520
+ return False
521
+
522
+ def LessEqual(s):
523
+ v1, v2 = comparison_safe_converter_for_pairs(f(s[1], c), f(s[2], c))
524
+ try:
525
+ return v1 <= v2
526
+ except TypeError:
527
+ return False
528
+
426
529
  constructs = {
427
530
  "Sum": Sum,
428
531
  "Add": Add,
@@ -431,6 +534,7 @@ def create_mathjson_solver(solver_parameters):
431
534
  ),
432
535
  "Constants": Constants,
433
536
  "Switch": Switch,
537
+ "StrictSwitch": StrictSwitch,
434
538
  "If": If,
435
539
  "Multiply": lambda s: reduce(
436
540
  lambda a, b: float(a) * float(b), [f(x, c) for x in s[1:]]
@@ -449,11 +553,17 @@ def create_mathjson_solver(solver_parameters):
449
553
  "Equal": lambda s: comparison_safe_converter(f(s[1], c))
450
554
  == comparison_safe_converter(f(s[2], c)),
451
555
  "StrictEqual": lambda s: f(s[1], c) == f(s[2], c),
452
- "Greater": lambda s: f(s[1], c) > f(s[2], c),
453
- "GreaterEqual": lambda s: f(s[1], c) >= f(s[2], c),
454
- "Less": lambda s: f(s[1], c) < f(s[2], c),
455
- "LessEqual": lambda s: f(s[1], c) <= f(s[2], c),
456
- "NotEqual": lambda s: f(s[1], c) != f(s[2], c),
556
+ # "Greater": lambda s: f(s[1], c) > f(s[2], c),
557
+ "Greater": Greater,
558
+ # "GreaterEqual": lambda s: f(s[1], c) >= f(s[2], c),
559
+ "GreaterEqual": GreaterEqual,
560
+ # "Less": lambda s: f(s[1], c) < f(s[2], c),
561
+ "Less": Less,
562
+ # "LessEqual": lambda s: f(s[1], c) <= f(s[2], c),
563
+ "LessEqual": LessEqual,
564
+ # "NotEqual": lambda s: f(s[1], c) != f(s[2], c),
565
+ "NotEqual": lambda s: comparison_safe_converter(f(s[1], c))
566
+ != comparison_safe_converter(f(s[2], c)),
457
567
  "Abs": lambda s: abs(f(s[1], c)),
458
568
  "Round": lambda s: (
459
569
  round(f(s[1], c), f(s[2], c))
@@ -484,6 +594,8 @@ def create_mathjson_solver(solver_parameters):
484
594
  # "IsDefined": lambda s: s[1] in c,
485
595
  "IsDefined": IsDefined,
486
596
  "Map": Map,
597
+ "StrictMap": StrictMap,
598
+ "Filter": Filter,
487
599
  "HasMatchingSublist": HasMatchingSublist,
488
600
  "Strptime": Strptime,
489
601
  "Strftime": Strftime,
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: mathjson-solver
3
- Version: 1.12.1
3
+ Version: 1.14.0
4
4
  Summary: Utilities for MathJSON evaluation
5
5
  Home-page: https://github.com/LongenesisLtd/mathjson-solver
6
6
  Author: Martins Mednis
@@ -18,6 +18,7 @@ Dynamic: classifier
18
18
  Dynamic: description
19
19
  Dynamic: description-content-type
20
20
  Dynamic: home-page
21
+ Dynamic: license-file
21
22
  Dynamic: project-url
22
23
  Dynamic: requires-python
23
24
  Dynamic: summary
@@ -26,7 +27,7 @@ Dynamic: summary
26
27
 
27
28
  [![PyPI](https://img.shields.io/pypi/v/mathjson-solver.svg)](https://pypi.org/project/mathjson-solver/)
28
29
  [![PyPI Downloads](https://static.pepy.tech/badge/mathjson-solver/month)](https://pepy.tech/projects/mathjson-solver)
29
- ![Coverage](https://img.shields.io/badge/coverage-93-green)
30
+ ![Coverage](https://img.shields.io/badge/coverage-91-green)
30
31
 
31
32
  _MathJSON Solver_ is a Python module to numerically evaluate MathJSON expressions, like `["Add", 1, 2]`. It is developed by [Longenesis](https://longenesis.com/team) to enable numerical evaluation of user provided mathematical expressions in Longenesis digital health products. Its development was inspired by [CortexJS](https://cortexjs.io/compute-engine/) Compute Engine.
32
33
 
@@ -73,11 +74,3 @@ Make sure you have `pytest` installed. Then `cd` into project directory and run:
73
74
  ```bash
74
75
  pytest
75
76
  ```
76
-
77
- ## Contributing Code
78
-
79
- We welcome your contributions in the form of pull requests.
80
-
81
- 1. Fork the repo;
82
- 2. Make improvements;
83
- 3. Make a pull request to share your improvements with the community and to include it into official release.
@@ -13,6 +13,10 @@ from mathjson_solver import create_solver, MathJSONException, extract_variables
13
13
  [
14
14
  ({}, ["Add", 2, 4, 3], 9),
15
15
  ({}, ["Sum", 2, 4, 3], 9),
16
+ ({}, ["Sum", 2, 4, 3, None], 9),
17
+ ({}, ["Add", 2, 4, "3"], 9),
18
+ ({}, ["Sum", 2, 4, "3"], 9),
19
+ ({}, ["Sum", 2, 4, "3", "abc"], 9),
16
20
  ({}, ["Sum", ["Array", 1, 1], 4, 3], 9),
17
21
  ({}, ["Sum", ["Array", 2, 4, 3]], 9),
18
22
  ({"a": 2}, ["Sum", "a", 4, 3], 9),
@@ -33,6 +37,16 @@ from mathjson_solver import create_solver, MathJSONException, extract_variables
33
37
  ({}, ["Round", ["Log", 2.7183], 3], 1.0),
34
38
  ({}, ["Log2", 8], 3),
35
39
  ({}, ["Log10", 1000], 3),
40
+ ],
41
+ )
42
+ def test_solver_arithmetics(parameters, expression, expected_result):
43
+ solver = create_solver(parameters)
44
+ assert solver(expression) == expected_result
45
+
46
+
47
+ @pytest.mark.parametrize(
48
+ "parameters, expression, expected_result",
49
+ [
36
50
  ({}, ["Equal", 10, 10], True),
37
51
  ({}, ["Equal", 10, 12], False),
38
52
  ({}, ["Equal", "aaa", "aaa"], True),
@@ -41,7 +55,6 @@ from mathjson_solver import create_solver, MathJSONException, extract_variables
41
55
  ({}, ["Equal", None, "1"], False),
42
56
  ({}, ["Equal", 1, "1"], True),
43
57
  ({}, ["Equal", None, False], True),
44
-
45
58
  ({}, ["StrictEqual", 1, "1"], False),
46
59
  ({}, ["StrictEqual", 10, 10], True),
47
60
  ({}, ["StrictEqual", 10, 12], False),
@@ -50,12 +63,12 @@ from mathjson_solver import create_solver, MathJSONException, extract_variables
50
63
  ({}, ["StrictEqual", "aaa", 11], False),
51
64
  ({}, ["StrictEqual", None, "1"], False),
52
65
  ({}, ["Equal", None, False], True),
53
-
54
-
55
66
  ({}, ["Greater", 1, 2], False),
56
67
  ({}, ["Greater", 2, -2], True),
68
+ ({}, ["Greater", "2", -2], True),
57
69
  ({}, ["GreaterEqual", 1, 1], True),
58
70
  ({}, ["GreaterEqual", 2, 1], True),
71
+ ({}, ["GreaterEqual", 2, "1"], True),
59
72
  ({}, ["GreaterEqual", 0, 0], True),
60
73
  ({}, ["GreaterEqual", 1, 2], False),
61
74
  ({}, ["Less", 1, 1], False),
@@ -67,6 +80,16 @@ from mathjson_solver import create_solver, MathJSONException, extract_variables
67
80
  ({}, ["NotEqual", 1, 2], True),
68
81
  ({}, ["NotEqual", "aaa", "bbb"], True),
69
82
  ({}, ["NotEqual", "aaa", 0], True),
83
+ ],
84
+ )
85
+ def test_solver_comparison(parameters, expression, expected_result):
86
+ solver = create_solver(parameters)
87
+ assert solver(expression) == expected_result
88
+
89
+
90
+ @pytest.mark.parametrize(
91
+ "parameters, expression, expected_result",
92
+ [
70
93
  # ({"a": 12}, ["IsDefined", "a"], True),
71
94
  ({"a": 12}, ["IsDefined", "b"], False),
72
95
  ({}, ["Abs", -3.5], 3.5),
@@ -132,16 +155,20 @@ from mathjson_solver import create_solver, MathJSONException, extract_variables
132
155
  # ],
133
156
  # 13
134
157
  # ),
158
+ # The next tests check the type-forgiving nature of Switch.
159
+ ({"value": 2}, ["Switch", "value", 0, ["1", 11], ["2", 22]], 22),
160
+ ({"value": "2"}, ["Switch", "value", 0, [1, 11], [2, 22]], 22),
161
+ # The next tests verify that the StrictSwitch is not forgiving.
162
+ ({"value": 2}, ["StrictSwitch", "value", 0, ["1", 11], ["2", 22]], 0),
163
+ ({"value": "2"}, ["StrictSwitch", "value", 0, [1, 11], [2, 22]], 0),
135
164
  ({"color": "red"}, ["Switch", "color", 0, ["blue", 10], ["red", 30]], 30),
136
165
  ({"color": "green"}, ["Switch", "color", 0, ["blue", 10], ["red", 30]], 0),
137
166
  ({"color": "green"}, ["Switch", "undefined", 0, ["blue", 10], ["red", 30]], 0),
138
167
  ({}, ["If", [["Equal", 1, 0], 10], [["Equal", 2, 2], 20], 9000], 20),
139
168
  # # The following tests that If don't fail on incompatible data types
140
169
  # ({}, ["If", [["Equal", 1, "1"], 10], [["Equal", "1", "1"], 20], -1], 20),
141
-
142
170
  # ({"a": 1}, ["If", [["Equal", "a", "1"], 10], [["Equal", "a", 1], 20], -1], 20),
143
171
  # ({"a": 1}, ["If", [["Equal", "a", 1], 10], [["Equal", "a", "1"], 20], -1], 10),
144
-
145
172
  ({"a": 10, "b": 10}, ["If", [["Equal", "a", "b"], 10], 9000], 10),
146
173
  ({"a": 10, "b": 20}, ["If", [["Equal", "a", "b"], 10], 9000], 9000),
147
174
  ({"a": 10}, ["If", [["Equal", "a", "b"], 10], 9000], 9000),
@@ -186,6 +213,7 @@ from mathjson_solver import create_solver, MathJSONException, extract_variables
186
213
  ({"a": ["Array", 2, 8]}, ["Average", "a"], 5),
187
214
  ({"a": 10, "b": 20}, ["Average", ["Array", "a", "b"]], 15),
188
215
  ({}, ["Length", ["Array", 1, 2, 3, 5, 2, 9]], 6),
216
+ ({}, ["Length", ["Array", 1, 2, 3, None]], 4),
189
217
  ({"a": ["Array", 1, 2, 3, 5, 2, 9]}, ["Length", "a"], 6),
190
218
  ({}, ["Length", ["Array"]], 0),
191
219
  # Next test illustrates an invalid formula, where 'Length' receives an invalid array.
@@ -271,7 +299,15 @@ from mathjson_solver import create_solver, MathJSONException, extract_variables
271
299
  ({}, ["Not", 0], True),
272
300
  ({}, ["Not", ["In", 2, ["Array", 1, 2, 3]]], False),
273
301
  ({}, ["Array", 2, 4, 3], ["Array", 2, 4, 3]),
302
+ ({}, ["StrictMap", ["Array", 1, 2, 3], ["Square"]], ["Array", 1, 4, 9]),
303
+ ({}, ["Filter", ["Array", 1, 2, 3], ["LessEqual"], 2], ["Array", 1, 2]),
304
+ ({}, ["Filter", ["Array", 1, 2, 3, "None"], ["LessEqual"], 2], ["Array", 1, 2]),
274
305
  ({}, ["Map", ["Array", 1, 2, 3], ["Square"]], ["Array", 1, 4, 9]),
306
+ (
307
+ {},
308
+ ["Map", ["Array", 1, 2, 3, None, "a"], ["Square"]],
309
+ ["Array", 1, 4, 9, None, "a"],
310
+ ),
275
311
  ({}, ["Map", ["Array", 1, 2, 3], ["Power"], 2], ["Array", 1, 4, 9]),
276
312
  (
277
313
  {},