Skip to content

Commit a62eafd

Browse files
authored
Merge pull request #140 from Deltares/prototype_REACT
Prototype react: make numpy and math available in formula based rule
2 parents f8df867 + 9ea132b commit a62eafd

3 files changed

Lines changed: 46 additions & 12 deletions

File tree

decoimpact/business/entities/rules/formula_rule.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,12 @@
1111
Formula Rule
1212
"""
1313

14+
# Import safe modules
15+
import math
1416
from argparse import ArgumentError as _ArgumentError
1517
from typing import Dict, List
1618

19+
import numpy
1720
from RestrictedPython import compile_restricted as _compile_restricted
1821
from RestrictedPython import safe_builtins as _safe_builtins
1922

@@ -85,23 +88,22 @@ def execute(self, values: Dict[str, float], logger: ILogger) -> float:
8588
return float(local_variables[self.formula_output_name])
8689

8790
def _setup_environment(self):
88-
self._safe_modules = frozenset(
89-
(
90-
"math",
91-
"numpy",
92-
)
93-
)
91+
# use standard libraries that are considered safe
92+
self._safe_modules_dict = {
93+
"math": math,
94+
"numpy": numpy,
95+
}
9496

9597
# Global data available in restricted code
96-
self._global_variables = (
97-
{ # MDK: THIS NEEDS TO CHANGE TO A MORE GENERAL APPROACH
98-
"__builtins__": {**_safe_builtins, "__import__": self._safe_import},
99-
}
100-
)
98+
self._global_variables = {
99+
"__builtins__": {**_safe_builtins, "__import__": self._safe_import},
100+
**self._safe_modules_dict,
101+
}
102+
101103
self._byte_code = None
102104

103105
def _safe_import(self, name, *args, **kwargs):
104106
# Redefine import, to only import from safe modules
105-
if name not in self._safe_modules:
107+
if name not in self._safe_modules_dict:
106108
raise _ArgumentError(None, f"Importing {name!r} is not allowed!")
107109
return __import__(name, *args, **kwargs)

docs/manual/rules/formula_rule.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,5 @@ When a formula results in a boolean, it will be converted to a float result. Mea
6969

7070
For more information on these operators click [here](https://www.w3schools.com/python/python_operators.asp).
7171

72+
It is also possible to use functions of the libraries math and numpy. These are accessible by calling their full module names inside the formula, for instance:
73+
"numpy.where(water_depth > 1.0)" or "math.ceil(water_level)".

tests/business/entities/rules/test_formula_rule.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,36 @@ def test_execute_multiplying_value_arrays():
6767
assert math.isclose(result_value, 6.0, abs_tol=1e-9)
6868

6969

70+
def test_execute_numpy_value_arrays():
71+
"""Test formula on value_arrays of a RuleBase"""
72+
73+
# Arrange
74+
logger = Mock(ILogger)
75+
rule = FormulaRule("test", ["val1"], "val1 * numpy.size(numpy.array([1, 3]))")
76+
values = {"val1": 1.0}
77+
78+
# Act
79+
result_value = rule.execute(values, logger)
80+
81+
# Assert
82+
assert math.isclose(result_value, 2.0, abs_tol=1e-9)
83+
84+
85+
def test_execute_math_value_arrays():
86+
"""Test formula on value_arrays of a RuleBase"""
87+
88+
# Arrange
89+
logger = Mock(ILogger)
90+
rule = FormulaRule("test", ["val1"], "val1 * math.isqrt(9)")
91+
values = {"val1": 2.0}
92+
93+
# Act
94+
result_value = rule.execute(values, logger)
95+
96+
# Assert
97+
assert math.isclose(result_value, 6.0, abs_tol=1e-9)
98+
99+
70100
@pytest.mark.parametrize(
71101
"input_value1, input_value2, expected_output_value",
72102
[(0.5, 10, 0.0), (11, 1.5, 1.0)],

0 commit comments

Comments
 (0)