From faf85b208fce746b4d15f89ab6c6b8ef0bc5a80b Mon Sep 17 00:00:00 2001 From: zachjweiner Date: Wed, 23 Jan 2019 13:42:40 -0600 Subject: [PATCH 01/15] added more functions --- pymbolic/functions.py | 31 +++++++++++++++++++++++++++++++ pymbolic/mapper/differentiator.py | 10 ++++++++++ 2 files changed, 41 insertions(+) diff --git a/pymbolic/functions.py b/pymbolic/functions.py index 4a0ae78..0b13692 100644 --- a/pymbolic/functions.py +++ b/pymbolic/functions.py @@ -49,3 +49,34 @@ def log(x): def exp(x): return primitives.Call( primitives.Lookup(primitives.Variable("math"), "exp"), (x,)) + + +def sinh(x): + return primitives.Call( + primitives.Lookup(primitives.Variable("math"), "tanh"), (x,)) + + +def cosh(x): + return primitives.Call( + primitives.Lookup(primitives.Variable("math"), "cosh"), (x,)) + + +def tanh(x): + return primitives.Call( + primitives.Lookup(primitives.Variable("math"), "sinh"), (x,)) + + +def expm1(x): + return primitives.Call( + primitives.Lookup(primitives.Variable("math"), "expm1"), (x,)) + + +def fabs(x): + return primitives.Call( + primitives.Lookup(primitives.Variable("math"), "fabs"), (x,)) + + +def sgn(x): + return primitives.Quotient(x, + primitives.Call( + primitives.Lookup(primitives.Variable("math"), "fabs"), (x,))) \ No newline at end of file diff --git a/pymbolic/mapper/differentiator.py b/pymbolic/mapper/differentiator.py index 86ac2f9..b59c9b1 100644 --- a/pymbolic/mapper/differentiator.py +++ b/pymbolic/mapper/differentiator.py @@ -50,6 +50,16 @@ def map_math_functions_by_name(i, func, pars): return primitives.quotient(1, pars[0]) elif f is math.exp and len(pars) == 1: return make_f("exp")(*pars) + elif f is math.sinh and len(pars) == 1: + return make_f("cosh")(*pars) + elif f is math.cosh and len(pars) == 1: + return make_f("sinh")(*pars) + elif f is math.tanh and len(pars) == 1: + return 1-make_f("tanh")(*pars)**2 + elif f is math.expm1 and len(pars) == 1: + return make_f("exp")(*pars) + elif f is math.fabs and len(pars) == 1: + return sgn(*pars) else: raise RuntimeError("unrecognized function, cannot differentiate") -- GitLab From 3eb064c35c065ba40538f080417cab6bf8cc0a2f Mon Sep 17 00:00:00 2001 From: zachjweiner Date: Wed, 23 Jan 2019 13:45:04 -0600 Subject: [PATCH 02/15] have to import sgn from functions --- pymbolic/mapper/differentiator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pymbolic/mapper/differentiator.py b/pymbolic/mapper/differentiator.py index b59c9b1..e0e0aea 100644 --- a/pymbolic/mapper/differentiator.py +++ b/pymbolic/mapper/differentiator.py @@ -59,6 +59,7 @@ def map_math_functions_by_name(i, func, pars): elif f is math.expm1 and len(pars) == 1: return make_f("exp")(*pars) elif f is math.fabs and len(pars) == 1: + from pymbolic.functions import sgn return sgn(*pars) else: raise RuntimeError("unrecognized function, cannot differentiate") -- GitLab From 8b4798e427cca2c467eb52b5dc6c7177e63f7c82 Mon Sep 17 00:00:00 2001 From: zachjweiner Date: Wed, 23 Jan 2019 13:47:50 -0600 Subject: [PATCH 03/15] appease flake8 --- pymbolic/functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymbolic/functions.py b/pymbolic/functions.py index 0b13692..beae9e6 100644 --- a/pymbolic/functions.py +++ b/pymbolic/functions.py @@ -79,4 +79,4 @@ def fabs(x): def sgn(x): return primitives.Quotient(x, primitives.Call( - primitives.Lookup(primitives.Variable("math"), "fabs"), (x,))) \ No newline at end of file + primitives.Lookup(primitives.Variable("math"), "fabs"), (x,))) -- GitLab From 52701712bdf11270a237cca2b5153e234e8721cc Mon Sep 17 00:00:00 2001 From: zachjweiner Date: Wed, 23 Jan 2019 14:26:16 -0600 Subject: [PATCH 04/15] permuted sinh and tanh --- pymbolic/functions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pymbolic/functions.py b/pymbolic/functions.py index beae9e6..bf94a4b 100644 --- a/pymbolic/functions.py +++ b/pymbolic/functions.py @@ -53,7 +53,7 @@ def exp(x): def sinh(x): return primitives.Call( - primitives.Lookup(primitives.Variable("math"), "tanh"), (x,)) + primitives.Lookup(primitives.Variable("math"), "sinh"), (x,)) def cosh(x): @@ -63,7 +63,7 @@ def cosh(x): def tanh(x): return primitives.Call( - primitives.Lookup(primitives.Variable("math"), "sinh"), (x,)) + primitives.Lookup(primitives.Variable("math"), "tanh"), (x,)) def expm1(x): -- GitLab From 60bff138e77c3b9c143f9240d9bc8827ee91d8a0 Mon Sep 17 00:00:00 2001 From: zachjweiner Date: Sun, 22 Sep 2019 14:12:02 -0500 Subject: [PATCH 05/15] replace math.sign with math.copysign --- pymbolic/functions.py | 36 ++++++++++++------------------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/pymbolic/functions.py b/pymbolic/functions.py index bf94a4b..2560356 100644 --- a/pymbolic/functions.py +++ b/pymbolic/functions.py @@ -23,60 +23,48 @@ THE SOFTWARE. """ -import pymbolic.primitives as primitives +import pymbolic.primitives as p def sin(x): - return primitives.Call( - primitives.Lookup(primitives.Variable("math"), "sin"), (x,)) + return p.Call(p.Lookup(p.Variable("math"), "sin"), (x,)) def cos(x): - return primitives.Call( - primitives.Lookup(primitives.Variable("math"), "cos"), (x,)) + return p.Call(p.Lookup(p.Variable("math"), "cos"), (x,)) def tan(x): - return primitives.Call( - primitives.Lookup(primitives.Variable("math"), "tan"), (x,)) + return p.Call(p.Lookup(p.Variable("math"), "tan"), (x,)) def log(x): - return primitives.Call( - primitives.Lookup(primitives.Variable("math"), "log"), (x,)) + return p.Call(p.Lookup(p.Variable("math"), "log"), (x,)) def exp(x): - return primitives.Call( - primitives.Lookup(primitives.Variable("math"), "exp"), (x,)) + return p.Call(p.Lookup(p.Variable("math"), "exp"), (x,)) def sinh(x): - return primitives.Call( - primitives.Lookup(primitives.Variable("math"), "sinh"), (x,)) + return p.Call(p.Lookup(p.Variable("math"), "sinh"), (x,)) def cosh(x): - return primitives.Call( - primitives.Lookup(primitives.Variable("math"), "cosh"), (x,)) + return p.Call(p.Lookup(p.Variable("math"), "cosh"), (x,)) def tanh(x): - return primitives.Call( - primitives.Lookup(primitives.Variable("math"), "tanh"), (x,)) + return p.Call(p.Lookup(p.Variable("math"), "tanh"), (x,)) def expm1(x): - return primitives.Call( - primitives.Lookup(primitives.Variable("math"), "expm1"), (x,)) + return p.Call(p.Lookup(p.Variable("math"), "expm1"), (x,)) def fabs(x): - return primitives.Call( - primitives.Lookup(primitives.Variable("math"), "fabs"), (x,)) + return p.Call(p.Lookup(p.Variable("math"), "fabs"), (x,)) def sgn(x): - return primitives.Quotient(x, - primitives.Call( - primitives.Lookup(primitives.Variable("math"), "fabs"), (x,))) + return p.Call(p.Lookup(p.Variable("math"), "copysign"), (1, x,)) -- GitLab From cd4b94733a69215842fe63e997ba909ee1e5aaaf Mon Sep 17 00:00:00 2001 From: zachjweiner Date: Sun, 22 Sep 2019 14:13:16 -0500 Subject: [PATCH 06/15] rename functions.sgn to functions.sign --- pymbolic/functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymbolic/functions.py b/pymbolic/functions.py index 2560356..b1a98df 100644 --- a/pymbolic/functions.py +++ b/pymbolic/functions.py @@ -66,5 +66,5 @@ def fabs(x): return p.Call(p.Lookup(p.Variable("math"), "fabs"), (x,)) -def sgn(x): +def sign(x): return p.Call(p.Lookup(p.Variable("math"), "copysign"), (1, x,)) -- GitLab From 2f438a935fc628edd745a1c9da091105e8139b76 Mon Sep 17 00:00:00 2001 From: zachjweiner Date: Sun, 22 Sep 2019 14:14:36 -0500 Subject: [PATCH 07/15] forgot to do rename sgn->sign in differentiator, too --- pymbolic/mapper/differentiator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pymbolic/mapper/differentiator.py b/pymbolic/mapper/differentiator.py index 4fa3a69..87de1e3 100644 --- a/pymbolic/mapper/differentiator.py +++ b/pymbolic/mapper/differentiator.py @@ -59,8 +59,8 @@ def map_math_functions_by_name(i, func, pars): elif f is math.expm1 and len(pars) == 1: return make_f("exp")(*pars) elif f is math.fabs and len(pars) == 1: - from pymbolic.functions import sgn - return sgn(*pars) + from pymbolic.functions import sign + return sign(*pars) else: raise RuntimeError("unrecognized function, cannot differentiate") -- GitLab From 8b0e6c1f677d1e38d1701c896c341758bc0ca566 Mon Sep 17 00:00:00 2001 From: zachjweiner Date: Sun, 22 Sep 2019 14:25:49 -0500 Subject: [PATCH 08/15] add allow_non_smooth and allow_discontinuity flags to differentiate to allow user to opt-in to derivatives of such functions --- pymbolic/mapper/differentiator.py | 32 ++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/pymbolic/mapper/differentiator.py b/pymbolic/mapper/differentiator.py index 87de1e3..25ac50c 100644 --- a/pymbolic/mapper/differentiator.py +++ b/pymbolic/mapper/differentiator.py @@ -31,7 +31,9 @@ import pymbolic.mapper import pymbolic.mapper.evaluator -def map_math_functions_by_name(i, func, pars): +def map_math_functions_by_name(i, func, pars, + allow_non_smooth=False, + allow_discontinuity=False): try: f = pymbolic.evaluate(func, {"math": math, "cmath": cmath}) except pymbolic.mapper.evaluator.UnknownVariableError: @@ -59,8 +61,15 @@ def map_math_functions_by_name(i, func, pars): elif f is math.expm1 and len(pars) == 1: return make_f("exp")(*pars) elif f is math.fabs and len(pars) == 1: - from pymbolic.functions import sign - return sign(*pars) + if allow_non_smooth: + return make_f("sign")(*pars) + else: + raise ValueError("fabs is not smooth") + elif f is math.copysign and len(pars) == 2: + if allow_discontinuity: + return 0 + else: + raise ValueError("sign is discontinuous") else: raise RuntimeError("unrecognized function, cannot differentiate") @@ -81,7 +90,8 @@ class DifferentiationMapper(pymbolic.mapper.RecursiveMapper): / (x + -1)**2**2 """ - def __init__(self, variable, func_map=map_math_functions_by_name): + def __init__(self, variable, func_map=map_math_functions_by_name, + allow_non_smooth=False, allow_discontinuity=False): """ :arg variable: A :class:`pymbolic.primitives.Variable` instance by which to differentiate. @@ -91,6 +101,8 @@ class DifferentiationMapper(pymbolic.mapper.RecursiveMapper): self.variable = variable self.function_map = func_map + self.allow_non_smooth = allow_non_smooth + self.allow_discontinuity = allow_discontinuity def rec_undiff(self, expr, *args): """This method exists for the benefit of subclasses that may need to @@ -110,7 +122,9 @@ class DifferentiationMapper(pymbolic.mapper.RecursiveMapper): def map_call(self, expr, *args): return pymbolic.flattened_sum( self.function_map( - i, expr.function, self.rec_undiff(expr.parameters, *args)) + i, expr.function, self.rec_undiff(expr.parameters, *args), + allow_non_smooth=self.allow_non_smooth, + allow_discontinuity=self.allow_discontinuity) * self.rec(par, *args) for i, par in enumerate(expr.parameters) ) @@ -200,7 +214,11 @@ class DifferentiationMapper(pymbolic.mapper.RecursiveMapper): def differentiate(expression, variable, - func_mapper=map_math_functions_by_name): + func_mapper=map_math_functions_by_name, + allow_non_smooth=False, + allow_discontinuity=False): if not isinstance(variable, (primitives.Variable, primitives.Subscript)): variable = primitives.make_variable(variable) - return DifferentiationMapper(variable, func_mapper)(expression) + return DifferentiationMapper(variable, func_mapper, + allow_non_smooth=allow_non_smooth, + allow_discontinuity=allow_discontinuity)(expression) -- GitLab From d5ad7786f1e3680033b108ed94ec8c117c386ed5 Mon Sep 17 00:00:00 2001 From: zachjweiner Date: Sun, 22 Sep 2019 14:27:17 -0500 Subject: [PATCH 09/15] add messages about flags to ValueErrors in map_math_functions_by_name --- pymbolic/mapper/differentiator.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pymbolic/mapper/differentiator.py b/pymbolic/mapper/differentiator.py index 25ac50c..2ad1898 100644 --- a/pymbolic/mapper/differentiator.py +++ b/pymbolic/mapper/differentiator.py @@ -64,12 +64,14 @@ def map_math_functions_by_name(i, func, pars, if allow_non_smooth: return make_f("sign")(*pars) else: - raise ValueError("fabs is not smooth") + raise ValueError("fabs is not smooth" + ", pass allow_non_smooth=True to return sign") elif f is math.copysign and len(pars) == 2: if allow_discontinuity: return 0 else: - raise ValueError("sign is discontinuous") + raise ValueError("sign is discontinuous" + ", pass allow_discontinuity=True to return 0") else: raise RuntimeError("unrecognized function, cannot differentiate") -- GitLab From 81afd8a22ce1d924ab77aa842b58d5d65424693f Mon Sep 17 00:00:00 2001 From: zachjweiner Date: Sun, 22 Sep 2019 17:35:29 -0500 Subject: [PATCH 10/15] doc allow_non_smooth and allow_discontinuity --- pymbolic/mapper/differentiator.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pymbolic/mapper/differentiator.py b/pymbolic/mapper/differentiator.py index 2ad1898..17c23f4 100644 --- a/pymbolic/mapper/differentiator.py +++ b/pymbolic/mapper/differentiator.py @@ -99,6 +99,18 @@ class DifferentiationMapper(pymbolic.mapper.RecursiveMapper): by which to differentiate. :arg func_map: A function for computing derivatives of function calls, signature ``(arg_index, function_variable, parameters)``. + :arg allow_non_smooth: Whether to allow differentiation of functions + which are not smooth (e.g., ``fabs``), i.e., by ignoring the + discontinuity in the resulting derivative. + Defaults to *False*. + :arg allow_discontinuity: Whether to allow differentiation of + which are not continuous (e.g., ``sign``), i.e., by ignoring the + discontinuity. + Defaults to *False*. + + .. versionchanged:: 2019.2 + + Added *allow_non_smooth* and *allow_discontinuity*. """ self.variable = variable -- GitLab From e8de9f501a3c470ece336d581b192151911e1a29 Mon Sep 17 00:00:00 2001 From: zachjweiner Date: Sun, 22 Sep 2019 17:44:34 -0500 Subject: [PATCH 11/15] add test for nonsmooth/discontinuous differentiating --- pymbolic/mapper/differentiator.py | 23 ++++++++++++----------- test/test_pymbolic.py | 19 +++++++++++++++++++ 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/pymbolic/mapper/differentiator.py b/pymbolic/mapper/differentiator.py index 17c23f4..f924f71 100644 --- a/pymbolic/mapper/differentiator.py +++ b/pymbolic/mapper/differentiator.py @@ -33,7 +33,7 @@ import pymbolic.mapper.evaluator def map_math_functions_by_name(i, func, pars, allow_non_smooth=False, - allow_discontinuity=False): + allow_discontinuous=False): try: f = pymbolic.evaluate(func, {"math": math, "cmath": cmath}) except pymbolic.mapper.evaluator.UnknownVariableError: @@ -62,16 +62,17 @@ def map_math_functions_by_name(i, func, pars, return make_f("exp")(*pars) elif f is math.fabs and len(pars) == 1: if allow_non_smooth: - return make_f("sign")(*pars) + from pymbolic.functions import sign + return sign(*pars) else: raise ValueError("fabs is not smooth" ", pass allow_non_smooth=True to return sign") elif f is math.copysign and len(pars) == 2: - if allow_discontinuity: + if allow_discontinuous: return 0 else: raise ValueError("sign is discontinuous" - ", pass allow_discontinuity=True to return 0") + ", pass allow_discontinuous=True to return 0") else: raise RuntimeError("unrecognized function, cannot differentiate") @@ -93,7 +94,7 @@ class DifferentiationMapper(pymbolic.mapper.RecursiveMapper): """ def __init__(self, variable, func_map=map_math_functions_by_name, - allow_non_smooth=False, allow_discontinuity=False): + allow_non_smooth=False, allow_discontinuous=False): """ :arg variable: A :class:`pymbolic.primitives.Variable` instance by which to differentiate. @@ -103,20 +104,20 @@ class DifferentiationMapper(pymbolic.mapper.RecursiveMapper): which are not smooth (e.g., ``fabs``), i.e., by ignoring the discontinuity in the resulting derivative. Defaults to *False*. - :arg allow_discontinuity: Whether to allow differentiation of + :arg allow_discontinuous: Whether to allow differentiation of which are not continuous (e.g., ``sign``), i.e., by ignoring the discontinuity. Defaults to *False*. .. versionchanged:: 2019.2 - Added *allow_non_smooth* and *allow_discontinuity*. + Added *allow_non_smooth* and *allow_discontinuous*. """ self.variable = variable self.function_map = func_map self.allow_non_smooth = allow_non_smooth - self.allow_discontinuity = allow_discontinuity + self.allow_discontinuous = allow_discontinuous def rec_undiff(self, expr, *args): """This method exists for the benefit of subclasses that may need to @@ -138,7 +139,7 @@ class DifferentiationMapper(pymbolic.mapper.RecursiveMapper): self.function_map( i, expr.function, self.rec_undiff(expr.parameters, *args), allow_non_smooth=self.allow_non_smooth, - allow_discontinuity=self.allow_discontinuity) + allow_discontinuous=self.allow_discontinuous) * self.rec(par, *args) for i, par in enumerate(expr.parameters) ) @@ -230,9 +231,9 @@ def differentiate(expression, variable, func_mapper=map_math_functions_by_name, allow_non_smooth=False, - allow_discontinuity=False): + allow_discontinuous=False): if not isinstance(variable, (primitives.Variable, primitives.Subscript)): variable = primitives.make_variable(variable) return DifferentiationMapper(variable, func_mapper, allow_non_smooth=allow_non_smooth, - allow_discontinuity=allow_discontinuity)(expression) + allow_discontinuous=allow_discontinuous)(expression) diff --git a/test/test_pymbolic.py b/test/test_pymbolic.py index 8ed651b..220bc79 100644 --- a/test/test_pymbolic.py +++ b/test/test_pymbolic.py @@ -641,6 +641,25 @@ def test_multiplicative_stringify_preserves_association(): assert_parse_roundtrip("(-1)*(((-1)*x) / 5)") +def test_differentiator_flags_for_nonsmooth_and_discontinuous(): + import pymbolic.functions as pf + from pymbolic.mapper.differentiator import differentiate + + x = prim.Variable('x') + + with pytest.raises(ValueError): + differentiate(pf.fabs(x), x) + + result = differentiate(pf.fabs(x), x, allow_non_smooth=True) + assert result == pf.sign(x) + + with pytest.raises(ValueError): + differentiate(pf.sign(x), x) + + result = differentiate(pf.sign(x), x, allow_discontinuous=True) + assert result == 0 + + if __name__ == "__main__": import sys if len(sys.argv) > 1: -- GitLab From 037e25ef19e35554caa65c314af8d0bd60930b5b Mon Sep 17 00:00:00 2001 From: zachjweiner Date: Sun, 22 Sep 2019 21:44:04 -0500 Subject: [PATCH 12/15] map_math_functions_by_name compares pymbolic dotted names rather than evaluating --- pymbolic/mapper/differentiator.py | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/pymbolic/mapper/differentiator.py b/pymbolic/mapper/differentiator.py index f924f71..e5d0995 100644 --- a/pymbolic/mapper/differentiator.py +++ b/pymbolic/mapper/differentiator.py @@ -34,40 +34,35 @@ import pymbolic.mapper.evaluator def map_math_functions_by_name(i, func, pars, allow_non_smooth=False, allow_discontinuous=False): - try: - f = pymbolic.evaluate(func, {"math": math, "cmath": cmath}) - except pymbolic.mapper.evaluator.UnknownVariableError: - raise RuntimeError("No derivative of non-constant function "+str(func)) - def make_f(name): return primitives.Lookup(primitives.Variable("math"), name) - if f is math.sin and len(pars) == 1: + if func == make_f("sin") and len(pars) == 1: return make_f("cos")(*pars) - elif f is math.cos and len(pars) == 1: + elif func == make_f("cos") and len(pars) == 1: return -make_f("sin")(*pars) - elif f is math.tan and len(pars) == 1: + elif func == make_f("tan") and len(pars) == 1: return make_f("tan")(*pars)**2+1 - elif f is math.log and len(pars) == 1: + elif func == make_f("log") and len(pars) == 1: return primitives.quotient(1, pars[0]) - elif f is math.exp and len(pars) == 1: + elif func == make_f("exp") and len(pars) == 1: return make_f("exp")(*pars) - elif f is math.sinh and len(pars) == 1: + elif func == make_f("sinh") and len(pars) == 1: return make_f("cosh")(*pars) - elif f is math.cosh and len(pars) == 1: + elif func == make_f("cosh") and len(pars) == 1: return make_f("sinh")(*pars) - elif f is math.tanh and len(pars) == 1: + elif func == make_f("tanh") and len(pars) == 1: return 1-make_f("tanh")(*pars)**2 - elif f is math.expm1 and len(pars) == 1: + elif func == make_f("expm1") and len(pars) == 1: return make_f("exp")(*pars) - elif f is math.fabs and len(pars) == 1: + elif func == make_f("fabs") and len(pars) == 1: if allow_non_smooth: from pymbolic.functions import sign return sign(*pars) else: raise ValueError("fabs is not smooth" ", pass allow_non_smooth=True to return sign") - elif f is math.copysign and len(pars) == 2: + elif func == make_f("copysign") and len(pars) == 2: if allow_discontinuous: return 0 else: -- GitLab From e24ec3493e2c1639b537f0e35fd7656f922732e9 Mon Sep 17 00:00:00 2001 From: zachjweiner Date: Sun, 22 Sep 2019 21:55:41 -0500 Subject: [PATCH 13/15] combine nonsmooth/discont flag into allowed_nonsmoothness --- pymbolic/mapper/differentiator.py | 46 ++++++++++++++----------------- test/test_pymbolic.py | 4 +-- 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/pymbolic/mapper/differentiator.py b/pymbolic/mapper/differentiator.py index e5d0995..1bad896 100644 --- a/pymbolic/mapper/differentiator.py +++ b/pymbolic/mapper/differentiator.py @@ -31,9 +31,7 @@ import pymbolic.mapper import pymbolic.mapper.evaluator -def map_math_functions_by_name(i, func, pars, - allow_non_smooth=False, - allow_discontinuous=False): +def map_math_functions_by_name(i, func, pars, allowed_nonsmoothness="none"): def make_f(name): return primitives.Lookup(primitives.Variable("math"), name) @@ -56,18 +54,20 @@ def map_math_functions_by_name(i, func, pars, elif func == make_f("expm1") and len(pars) == 1: return make_f("exp")(*pars) elif func == make_f("fabs") and len(pars) == 1: - if allow_non_smooth: + if allowed_nonsmoothness in ["continuous", "discontinuous"]: from pymbolic.functions import sign return sign(*pars) else: raise ValueError("fabs is not smooth" - ", pass allow_non_smooth=True to return sign") + ", pass allowed_nonsmoothness='continuous' " + "to return sign") elif func == make_f("copysign") and len(pars) == 2: - if allow_discontinuous: + if allowed_nonsmoothness == "discontinuous": return 0 else: raise ValueError("sign is discontinuous" - ", pass allow_discontinuous=True to return 0") + ", pass allowed_nonsmoothness='discontinuous' " + "to return 0") else: raise RuntimeError("unrecognized function, cannot differentiate") @@ -89,30 +89,26 @@ class DifferentiationMapper(pymbolic.mapper.RecursiveMapper): """ def __init__(self, variable, func_map=map_math_functions_by_name, - allow_non_smooth=False, allow_discontinuous=False): + allowed_nonsmoothness="none"): """ :arg variable: A :class:`pymbolic.primitives.Variable` instance by which to differentiate. :arg func_map: A function for computing derivatives of function calls, signature ``(arg_index, function_variable, parameters)``. - :arg allow_non_smooth: Whether to allow differentiation of functions - which are not smooth (e.g., ``fabs``), i.e., by ignoring the - discontinuity in the resulting derivative. - Defaults to *False*. - :arg allow_discontinuous: Whether to allow differentiation of - which are not continuous (e.g., ``sign``), i.e., by ignoring the - discontinuity. - Defaults to *False*. + :arg allowed_nonsmoothness: Whether to allow differentiation of + functions which are not smooth or continuous. + Pass ``"continuous"`` to allow nonsmooth but not discontinuous + functions or ``"discontinuous"`` to allow both. + Defaults to ``"none"``, in which case neither is allowed. .. versionchanged:: 2019.2 - Added *allow_non_smooth* and *allow_discontinuous*. + Added *allowed_nonsmoothness*. """ self.variable = variable self.function_map = func_map - self.allow_non_smooth = allow_non_smooth - self.allow_discontinuous = allow_discontinuous + self.allowed_nonsmoothness = allowed_nonsmoothness def rec_undiff(self, expr, *args): """This method exists for the benefit of subclasses that may need to @@ -133,8 +129,7 @@ class DifferentiationMapper(pymbolic.mapper.RecursiveMapper): return pymbolic.flattened_sum( self.function_map( i, expr.function, self.rec_undiff(expr.parameters, *args), - allow_non_smooth=self.allow_non_smooth, - allow_discontinuous=self.allow_discontinuous) + allowed_nonsmoothness=self.allowed_nonsmoothness) * self.rec(par, *args) for i, par in enumerate(expr.parameters) ) @@ -225,10 +220,9 @@ class DifferentiationMapper(pymbolic.mapper.RecursiveMapper): def differentiate(expression, variable, func_mapper=map_math_functions_by_name, - allow_non_smooth=False, - allow_discontinuous=False): + allowed_nonsmoothness="none"): if not isinstance(variable, (primitives.Variable, primitives.Subscript)): variable = primitives.make_variable(variable) - return DifferentiationMapper(variable, func_mapper, - allow_non_smooth=allow_non_smooth, - allow_discontinuous=allow_discontinuous)(expression) + return DifferentiationMapper( + variable, func_mapper, allowed_nonsmoothness=allowed_nonsmoothness + )(expression) diff --git a/test/test_pymbolic.py b/test/test_pymbolic.py index 220bc79..2d6f3f7 100644 --- a/test/test_pymbolic.py +++ b/test/test_pymbolic.py @@ -650,13 +650,13 @@ def test_differentiator_flags_for_nonsmooth_and_discontinuous(): with pytest.raises(ValueError): differentiate(pf.fabs(x), x) - result = differentiate(pf.fabs(x), x, allow_non_smooth=True) + result = differentiate(pf.fabs(x), x, allowed_nonsmoothness="continuous") assert result == pf.sign(x) with pytest.raises(ValueError): differentiate(pf.sign(x), x) - result = differentiate(pf.sign(x), x, allow_discontinuous=True) + result = differentiate(pf.sign(x), x, allowed_nonsmoothness="discontinuous") assert result == 0 -- GitLab From 82d57c96c930ce9ff9761833a2bc5877f18f8201 Mon Sep 17 00:00:00 2001 From: zachjweiner Date: Sun, 22 Sep 2019 21:56:39 -0500 Subject: [PATCH 14/15] appease flake8 --- pymbolic/mapper/differentiator.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pymbolic/mapper/differentiator.py b/pymbolic/mapper/differentiator.py index 1bad896..986ef76 100644 --- a/pymbolic/mapper/differentiator.py +++ b/pymbolic/mapper/differentiator.py @@ -22,9 +22,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -import math -import cmath - import pymbolic import pymbolic.primitives as primitives import pymbolic.mapper -- GitLab From c49fb8687c3ce225c98d7bbafc74df0f2a7b1731 Mon Sep 17 00:00:00 2001 From: zachjweiner Date: Sun, 22 Sep 2019 22:06:48 -0500 Subject: [PATCH 15/15] check that passed value allowed_nonsmoothness is allowed --- pymbolic/mapper/differentiator.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pymbolic/mapper/differentiator.py b/pymbolic/mapper/differentiator.py index 986ef76..beb901a 100644 --- a/pymbolic/mapper/differentiator.py +++ b/pymbolic/mapper/differentiator.py @@ -105,6 +105,9 @@ class DifferentiationMapper(pymbolic.mapper.RecursiveMapper): self.variable = variable self.function_map = func_map + if allowed_nonsmoothness not in ["none", "continuous", "discontinuous"]: + raise ValueError("allowed_nonsmoothness=%s is not a valid option" + % allowed_nonsmoothness) self.allowed_nonsmoothness = allowed_nonsmoothness def rec_undiff(self, expr, *args): -- GitLab