mathjson-solver 1.19.0__tar.gz → 1.20.1__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 (108) hide show
  1. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/PKG-INFO +1 -1
  2. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/SECURITY.md +2 -2
  3. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/docs/README.md +42 -1
  4. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/pyproject.toml +1 -1
  5. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/src/mathjson_solver/__main__.py +12 -3
  6. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/tests/test_datetime.py +14 -0
  7. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/tests/test_with_pytest.py +17 -1
  8. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  9. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/.github/workflows/python-app.yml +0 -0
  10. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/.github/workflows/test-coverage.yml +0 -0
  11. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/.gitignore +0 -0
  12. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/CODE_OF_CONDUCT.md +0 -0
  13. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/CONTRIBUTING.md +0 -0
  14. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/LICENSE +0 -0
  15. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/README.md +0 -0
  16. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/docs/integration-test-cases.md +0 -0
  17. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/logo.png +0 -0
  18. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/logo.svg +0 -0
  19. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/src/mathjson_solver/__init__.py +0 -0
  20. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/tests/test_TrapezoidalIntegratel.py +0 -0
  21. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/tests/test_atindex.py +0 -0
  22. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/tests/test_cumulative.py +0 -0
  23. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/tests/test_equal.py +0 -0
  24. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/tests/test_exceptions.py +0 -0
  25. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/tests/test_extract_variables.py +0 -0
  26. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/tests/test_findinterval.py +0 -0
  27. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/tests/test_gail_model.py +0 -0
  28. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/tests/test_interpolate.py +0 -0
  29. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/tests/test_range.py +0 -0
  30. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/tests/test_reduce_simple.py +0 -0
  31. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/tests/test_scalars.py +0 -0
  32. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/tests/test_sets.py +0 -0
  33. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/tests/test_simple_cases.py +0 -0
  34. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/tests/test_slice.py +0 -0
  35. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/tests/test_state_tuples_reduce.py +0 -0
  36. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommited2/NCI_50_CASES_WORKSHEET.md +0 -0
  37. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommited2/NCI_AGE35-39_ANALYSIS.md +0 -0
  38. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommited2/NCI_AGE35-44_ANALYSIS.md +0 -0
  39. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommited2/NCI_COMPARISON_REPORT.md +0 -0
  40. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommited2/NCI_COMPLETE_VALIDATION_ANALYSIS.md +0 -0
  41. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommited2/NCI_VALIDATION_WORKSHEET.md +0 -0
  42. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommited2/R_CODE_VERIFICATION_ANALYSIS.md +0 -0
  43. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommited2/corrected_comparison.txt +0 -0
  44. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommited2/final_comparison.txt +0 -0
  45. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommited2/full_comparison.md +0 -0
  46. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommited2/gail_continuous_model_visualization.html +0 -0
  47. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommited2/gail_continuous_model_visualization.md +0 -0
  48. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommited2/generate_comprehensive_comparison.py +0 -0
  49. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommited2/test_interval_analysis.py +0 -0
  50. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommited2/updated_comparison.txt +0 -0
  51. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/BCRA_2.0/BCRA/DESCRIPTION +0 -0
  52. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/BCRA_2.0/BCRA/NAMESPACE +0 -0
  53. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/BCRA_2.0/BCRA/R/absolute.risk.R +0 -0
  54. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/BCRA_2.0/BCRA/R/check.summary.R +0 -0
  55. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/BCRA_2.0/BCRA/R/error.table.R +0 -0
  56. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/BCRA_2.0/BCRA/R/error.table.all.R +0 -0
  57. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/BCRA_2.0/BCRA/R/list.constants.R +0 -0
  58. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/BCRA_2.0/BCRA/R/recode.check.R +0 -0
  59. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/BCRA_2.0/BCRA/R/relative.risk.R +0 -0
  60. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/BCRA_2.0/BCRA/R/risk.summary.R +0 -0
  61. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/BCRA_2.0/BCRA/data/BrCa_1_AR.rda +0 -0
  62. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/BCRA_2.0/BCRA/data/BrCa_beta.rda +0 -0
  63. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/BCRA_2.0/BCRA/data/BrCa_lambda1.rda +0 -0
  64. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/BCRA_2.0/BCRA/data/BrCa_lambda2.rda +0 -0
  65. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/BCRA_2.0/BCRA/data/exampledata.rda +0 -0
  66. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/BCRA_2.0/BCRA/man/BCRA-package.Rd +0 -0
  67. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/BCRA_2.0/BCRA/man/BrCa_1_AR.Rd +0 -0
  68. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/BCRA_2.0/BCRA/man/BrCa_beta.Rd +0 -0
  69. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/BCRA_2.0/BCRA/man/BrCa_lambda1.Rd +0 -0
  70. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/BCRA_2.0/BCRA/man/BrCa_lambda2.Rd +0 -0
  71. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/BCRA_2.0/BCRA/man/absolute.risk.Rd +0 -0
  72. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/BCRA_2.0/BCRA/man/check.summary.Rd +0 -0
  73. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/BCRA_2.0/BCRA/man/error.table.Rd +0 -0
  74. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/BCRA_2.0/BCRA/man/error.table.all.Rd +0 -0
  75. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/BCRA_2.0/BCRA/man/exampledata.Rd +0 -0
  76. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/BCRA_2.0/BCRA/man/list.constants.Rd +0 -0
  77. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/BCRA_2.0/BCRA/man/recode.check.Rd +0 -0
  78. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/BCRA_2.0/BCRA/man/relative.risk.Rd +0 -0
  79. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/BCRA_2.0/BCRA/man/risk.summary.Rd +0 -0
  80. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/CLEAN_MODEL_USAGE.md +0 -0
  81. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/COSTANTINO1999_RESULTS_TABLE.md +0 -0
  82. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/EXACT_PATIENT_EXAMPLES.md +0 -0
  83. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/FINAL_IMPLEMENTATION_SUMMARY.md +0 -0
  84. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/GAIL_MODEL_IMPLEMENTATION_REPORT.md +0 -0
  85. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/INTEGRATION_MODEL_VALIDATION_COMPARISON.md +0 -0
  86. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/NCI_VALIDATION_STRATEGY.md +0 -0
  87. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/best_so_far/man_test_gail_model_nci_validated_no_beta.py +0 -0
  88. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/best_so_far/man_test_nci_discrete_implementation_no_beta.py +0 -0
  89. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/gail_continuous_model_mathjson.json +0 -0
  90. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/publications/costantino_1999.pdf +0 -0
  91. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/publications/gail_1989.pdf +0 -0
  92. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/tests/generate_exact_patient_examples.py +0 -0
  93. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/tests/test_bcpt_validation_integration.py +0 -0
  94. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/tests/test_gail_approximated_model_nci_validated.py +0 -0
  95. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/tests/test_gail_exact_nci_implementation.py +0 -0
  96. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/tests/test_gail_exact_with_cumulative.py +0 -0
  97. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/tests/test_gail_exact_with_reduce.py +0 -0
  98. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/tests/test_gail_model_costantino_integration.py +0 -0
  99. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/tests/test_gail_model_nci_validated_fixed.py +0 -0
  100. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/tests/test_nci_calculator_validation.py +0 -0
  101. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/unsorted_uncommitted/tests/test_patient_examples_comparison.py +0 -0
  102. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/untracked/chart.csv +0 -0
  103. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/untracked/cvd_risk.json +0 -0
  104. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/untracked/image.png +0 -0
  105. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/untracked/score2.csv +0 -0
  106. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/untracked/score2_mid.csv +0 -0
  107. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/untracked/score2_results2.xlsx +0 -0
  108. {mathjson_solver-1.19.0 → mathjson_solver-1.20.1}/untracked/score2_table.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mathjson-solver
3
- Version: 1.19.0
3
+ Version: 1.20.1
4
4
  Summary: Utilities for MathJSON evaluation
5
5
  Project-URL: Homepage, https://github.com/LongenesisLtd/mathjson-solver
6
6
  Project-URL: Bug Tracker, https://github.com/LongenesisLtd/mathjson-solver/issues
@@ -5,8 +5,8 @@
5
5
 
6
6
  | Version | Supported |
7
7
  | ------- | ------------------ |
8
- | 1.13.x | :white_check_mark: |
9
- | < 1.13.x | :x: |
8
+ | 1.20.x | :white_check_mark: |
9
+ | < 1.20.x | :x: |
10
10
 
11
11
 
12
12
  ## Reporting a Vulnerability
@@ -309,6 +309,26 @@ The following example has two constants defined - `x=10` and `y=20`. Then the su
309
309
  ]
310
310
  ```
311
311
 
312
+ #### Null propagation
313
+
314
+ If a constant definition raises an exception (e.g. because a referenced parameter is missing), that constant is set to `None` instead of crashing the entire expression. This allows `If` to select the valid branch at runtime:
315
+
316
+ ```python
317
+ # With parameters = {"new_val": 22.5} (old_val is missing)
318
+ [
319
+ "Constants",
320
+ ["c_old", ["Divide", "old_val", 2]], # raises → None
321
+ ["c_new", ["Divide", "new_val", 2]], # 11.25
322
+ ["If",
323
+ [["Greater", "c_new", 0], "c_new"],
324
+ "c_old"
325
+ ]
326
+ ]
327
+ # Result: 11.25 (c_old was never needed)
328
+ ```
329
+
330
+ A constant that failed to compute resolves to `None`, which is falsy. Use `["IsFalse", "c_old"]` to detect it (or `["IsTrue", "c_old"]` to confirm it computed successfully).
331
+
312
332
  ### If Statement
313
333
  ```
314
334
  [
@@ -779,13 +799,33 @@ Converts a value to a string representation.
779
799
  ```
780
800
 
781
801
  ### IsDefined
782
- **Note: This is a placeholder function for future implementation.** Supposed to check if a parameter name exists in the parameters dictionary.
802
+ Returns `True` if the given name exists as a solver parameter or as a `Constants`-defined constant, `False` otherwise.
783
803
 
784
804
  ```python
785
805
  # With parameters = {"a": 12}
806
+ ["IsDefined", "a"] # True
786
807
  ["IsDefined", "b"] # False
808
+
809
+ # Inside Constants
810
+ ["Constants", ["x", 5], ["IsDefined", "x"]] # True
811
+ ["Constants", ["x", 5], ["IsDefined", "y"]] # False
812
+ ```
813
+
814
+ ### IsUndefined
815
+ The logical complement of `IsDefined`. Returns `True` if the name is not defined, `False` if it is.
816
+
817
+ ```python
818
+ # With parameters = {"a": 12}
819
+ ["IsUndefined", "a"] # False
820
+ ["IsUndefined", "b"] # True
821
+
822
+ # Inside Constants
823
+ ["Constants", ["x", 5], ["IsUndefined", "x"]] # False
824
+ ["Constants", ["x", 5], ["IsUndefined", "y"]] # True
787
825
  ```
788
826
 
827
+ Note: when a `Constants` definition raises an exception, that constant is set to `None` (see null-propagation below). In that case `IsUndefined` still returns `False` — the name is defined, just with a `None` value. Use `["IsFalse", "val"]` to detect a failed constant (since `None` is falsy).
828
+
789
829
  ---
790
830
 
791
831
  ## Date and Time Functions
@@ -1166,6 +1206,7 @@ Finds the interval index where a value falls within a sorted array of bounds. Re
1166
1206
  - [Float](#float) - Convert to float
1167
1207
  - [Str](#str) - Convert to string
1168
1208
  - [IsDefined](#isdefined) - Check if defined
1209
+ - [IsUndefined](#isundefined) - Check if not defined
1169
1210
 
1170
1211
  ### Date and Time Functions
1171
1212
  - [Today](#today) - Current date (ISO string)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "mathjson-solver"
7
- version = "1.19.0"
7
+ version = "1.20.1"
8
8
  authors = [{name = "Martins Mednis", email = "mrt@mednis.info"}]
9
9
  description = "Utilities for MathJSON evaluation"
10
10
  readme = "README.md"
@@ -430,7 +430,10 @@ def create_mathjson_solver(solver_parameters):
430
430
 
431
431
  def Constants(s):
432
432
  for x in s[1:-1]:
433
- c[x[0]] = f(x[1], c)
433
+ try:
434
+ c[x[0]] = f(x[1], c)
435
+ except Exception:
436
+ c[x[0]] = None
434
437
  return f(s[-1], c)
435
438
 
436
439
  def Switch(s):
@@ -614,6 +617,8 @@ def create_mathjson_solver(solver_parameters):
614
617
 
615
618
  def Strftime(s):
616
619
  dt = _try_parse_datetime(f(s[1], c))
620
+ if not isinstance(dt, (datetime.datetime, datetime.date)):
621
+ raise ValueError(f"Strftime: could not parse input as datetime: {dt!r}")
617
622
  parameters = f(s[2], c)
618
623
  return dt.strftime(parameters)
619
624
 
@@ -652,8 +657,10 @@ def create_mathjson_solver(solver_parameters):
652
657
  # lambda s: f"{f(s[1], c)}" == f"{f(s[2], c)}",
653
658
 
654
659
  def IsDefined(s):
655
- # Todo: This does not work
656
- return s[1] in c.keys()
660
+ return s[1] in solver_parameters or s[1] in c
661
+
662
+ def IsUndefined(s):
663
+ return not IsDefined(s)
657
664
 
658
665
  def Greater(s):
659
666
  v1, v2 = comparison_safe_converter_for_pairs(f(s[1], c), f(s[2], c))
@@ -1101,6 +1108,7 @@ def create_mathjson_solver(solver_parameters):
1101
1108
  "Not": Not,
1102
1109
  # "IsDefined": lambda s: s[1] in c,
1103
1110
  "IsDefined": IsDefined,
1111
+ "IsUndefined": IsUndefined,
1104
1112
  "Map": Map,
1105
1113
  "StrictMap": StrictMap,
1106
1114
  "Filter": Filter,
@@ -1230,6 +1238,7 @@ def extract_variables(s: Union[list, int, float, str], li: set, ignore_list: set
1230
1238
  "Str",
1231
1239
  "Not",
1232
1240
  "IsDefined",
1241
+ "IsUndefined",
1233
1242
  "Map",
1234
1243
  "HasMatchingSublist",
1235
1244
  "Strptime",
@@ -134,6 +134,20 @@ class TestStrftime:
134
134
  result = solver(["Strftime", "2025-06-15T14:30:45", "%Y-%m-%d %H:%M:%S"])
135
135
  assert result == "2025-06-15 14:30:45"
136
136
 
137
+ def test_strftime_raises_on_unparseable_input(self):
138
+ """Strftime must raise MathJSONException (not AttributeError) for unparseable input."""
139
+ from mathjson_solver import MathJSONException
140
+ solver = create_solver({})
141
+ with pytest.raises(MathJSONException):
142
+ solver(["Strftime", "not-a-date", "%Y-%m-%d"])
143
+
144
+ def test_strftime_raises_on_none_input(self):
145
+ """Strftime must raise MathJSONException when input resolves to None."""
146
+ from mathjson_solver import MathJSONException
147
+ solver = create_solver({})
148
+ with pytest.raises(MathJSONException):
149
+ solver(["Strftime", None, "%Y-%m-%d"])
150
+
137
151
 
138
152
  class TestDatetimeChaining:
139
153
  """Test complex datetime operation chains."""
@@ -11,8 +11,12 @@ from mathjson_solver import create_solver
11
11
  @pytest.mark.parametrize(
12
12
  "parameters, expression, expected_result",
13
13
  [
14
- # ({"a": 12}, ["IsDefined", "a"], True),
14
+ ({"a": 12}, ["IsDefined", "a"], True),
15
15
  ({"a": 12}, ["IsDefined", "b"], False),
16
+ ({}, ["Constants", ["x", 5], ["IsDefined", "x"]], True),
17
+ ({"a": 12}, ["IsUndefined", "a"], False),
18
+ ({"a": 12}, ["IsUndefined", "b"], True),
19
+ ({}, ["Constants", ["x", 5], ["IsUndefined", "x"]], False),
16
20
  ({}, ["Abs", -3.5], 3.5),
17
21
  ({}, ["Round", -5.123456, 2], -5.12),
18
22
  ({}, ["Round", -5.123456, 0], -5),
@@ -21,6 +25,18 @@ from mathjson_solver import create_solver
21
25
  ({}, ["Constants", ["a", 1], ["b", 2], "a"], 1),
22
26
  ({}, ["Constants", ["a", 1], ["b", 2], ["c", 100], ["Sum", "a", "b"]], 3),
23
27
  ({}, ["Constants", ["a", 1], ["b", ["Add", 2, "a"]], "b"], 3),
28
+ # Constants null-propagation: uncomputable constant becomes None,
29
+ # allowing If to select the computable branch.
30
+ (
31
+ {"x": 10},
32
+ ["Constants", ["good", "x"], ["bad", ["Divide", "missing", 2]], ["If", [["Greater", "good", 0], "good"], "bad"]],
33
+ 10,
34
+ ),
35
+ (
36
+ {"x": 10},
37
+ ["Constants", ["bad", ["Divide", "missing", 2]], ["good", "x"], ["If", [["Greater", "good", 0], "good"], "bad"]],
38
+ 10,
39
+ ),
24
40
  ({"x": 1}, ["Add", 2, "x"], 3),
25
41
  ({"value": 1}, ["Switch", "value", 0, [1, 11], [2, 22]], 11),
26
42
  ({"value": 3}, ["Switch", "value", 0, [1, 11], [2, 22]], 0),