1#  ___________________________________________________________________________
2#
3#  Pyomo: Python Optimization Modeling Objects
4#  Copyright 2017 National Technology and Engineering Solutions of Sandia, LLC
5#  Under the terms of Contract DE-NA0003525 with National Technology and
6#  Engineering Solutions of Sandia, LLC, the U.S. Government retains certain
7#  rights in this software.
8#  This software is distributed under the 3-clause BSD License.
9#  ___________________________________________________________________________
10
11import enum
12from .diff_with_sympy import differentiate as sympy_diff
13from .diff_with_pyomo import reverse_sd, reverse_ad
14
15
16class Modes(str, enum.Enum):
17    sympy='sympy'
18    reverse_symbolic='reverse_symbolic'
19    reverse_numeric='reverse_numeric'
20
21    # Overloading __str__ is needed to match the behavior of the old
22    # pyutilib.enum class (removed June 2020). There are spots in the
23    # code base that expect the string representation for items in the
24    # enum to not include the class name. New uses of enum shouldn't
25    # need to do this.
26    def __str__(self):
27        return self.value
28
29
30def differentiate(expr, wrt=None, wrt_list=None, mode=Modes.reverse_numeric):
31    """Return derivative of expression.
32
33    This function returns the derivative of expr with respect to one or
34    more variables.  The type of the return value depends on the
35    arguments wrt, wrt_list, and mode. See below for details.
36
37    Parameters
38    ----------
39    expr: pyomo.core.expr.numeric_expr.ExpressionBase
40        The expression to differentiate
41    wrt: pyomo.core.base.var._GeneralVarData
42        If specified, this function will return the derivative with
43        respect to wrt. wrt is normally a _GeneralVarData, but could
44        also be a _ParamData. wrt and wrt_list cannot both be specified.
45    wrt_list: list of pyomo.core.base.var._GeneralVarData
46        If specified, this function will return the derivative with
47        respect to each element in wrt_list.  A list will be returned
48        where the values are the derivatives with respect to the
49        corresponding entry in wrt_list.
50    mode: pyomo.core.expr.calculus.derivatives.Modes
51        Specifies the method to use for differentiation. Should be one
52        of the members of the Modes enum:
53
54            Modes.sympy:
55                The pyomo expression will be converted to a sympy
56                expression. Differentiation will then be done with
57                sympy, and the result will be converted back to a pyomo
58                expression.  The sympy mode only does symbolic
59                differentiation. The sympy mode requires exactly one of
60                wrt and wrt_list to be specified.
61            Modes.reverse_symbolic:
62                Symbolic differentiation will be performed directly with
63                the pyomo expression in reverse mode. If neither wrt nor
64                wrt_list are specified, then a ComponentMap is returned
65                where there will be a key for each node in the
66                expression tree, and the values will be the symbolic
67                derivatives.
68            Modes.reverse_numeric:
69                Numeric differentiation will be performed directly with
70                the pyomo expression in reverse mode. If neither wrt nor
71                wrt_list are specified, then a ComponentMap is returned
72                where there will be a key for each node in the
73                expression tree, and the values will be the floating
74                point values of the derivatives at the current values of
75                the variables.
76
77    Returns
78    -------
79    res: float, :py:class:`ExpressionBase`, :py:class:`ComponentMap`, or list
80        The value or expression of the derivative(s)
81
82    """
83
84    if mode == Modes.reverse_numeric or mode == Modes.reverse_symbolic:
85        if mode == Modes.reverse_numeric:
86            res = reverse_ad(expr=expr)
87        else:
88            res = reverse_sd(expr=expr)
89
90        if wrt is not None:
91            if wrt_list is not None:
92                raise ValueError(
93                    'differentiate(): Cannot specify both wrt and wrt_list.')
94            if wrt in res:
95                res = res[wrt]
96            else:
97                res = 0
98        elif wrt_list is not None:
99            _res = list()
100            for _wrt in wrt_list:
101                if _wrt in res:
102                    _res.append(res[_wrt])
103                else:
104                    _res.append(0)
105            res = _res
106    elif mode is Modes.sympy:
107        res = sympy_diff(expr=expr, wrt=wrt, wrt_list=wrt_list)
108    else:
109        raise ValueError(
110            'differentiate(): Unrecognized differentiation mode: {0}'.format(
111                mode))
112
113    return res
114
115
116differentiate.Modes = Modes
117