-
-
Notifications
You must be signed in to change notification settings - Fork 606
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Issue 665 jac class #670
Issue 665 jac class #670
Changes from all commits
d1acc43
8834f39
c6d5717
fbb18a4
d9b3cf0
6837960
593370d
719bf55
2559aef
e4bfe1a
9d1579c
93d9862
d1b0b2d
ddc30ed
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
Jacobian | ||
======== | ||
|
||
.. autoclass:: pybamm.Jacobian | ||
:members: |
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
# | ||
# Calculate the Jacobian of a symbol | ||
# | ||
import pybamm | ||
|
||
|
||
class Jacobian(object): | ||
def __init__(self, known_jacs=None): | ||
self._known_jacs = known_jacs or {} | ||
|
||
def jac(self, symbol, variable): | ||
""" | ||
This function recurses down the tree, computing the Jacobian using | ||
the Jacobians defined in classes derived from pybamm.Symbol. E.g. the | ||
Jacobian of a 'pybamm.Multiplication' is computed via the product rule. | ||
If the Jacobian of a symbol has already been calculated, the stored value | ||
is returned. | ||
Note: The Jacobian is the derivative of a symbol with respect to a (slice of) | ||
a State Vector. | ||
Parameters | ||
---------- | ||
symbol : :class:`pybamm.Symbol` | ||
The symbol to calculate the Jacobian of | ||
variable : :class:`pybamm.Symbol` | ||
The variable with respect to which to differentiate | ||
Returns | ||
------- | ||
:class:`pybamm.Symbol` | ||
Symbol representing the Jacobian | ||
""" | ||
|
||
try: | ||
return self._known_jacs[symbol.id] | ||
except KeyError: | ||
jac = self._jac(symbol, variable) | ||
self._known_jacs[symbol.id] = jac | ||
return jac | ||
|
||
def _jac(self, symbol, variable): | ||
""" See :meth:`Jacobian.jac()`. """ | ||
|
||
if isinstance(symbol, pybamm.BinaryOperator): | ||
left, right = symbol.children | ||
# process children | ||
left_jac = self.jac(left, variable) | ||
right_jac = self.jac(right, variable) | ||
# Need to treat outer differently. If the left child of an Outer | ||
# evaluates to number then we need to return a matrix of zeros | ||
# of the correct size, which requires variable.evaluation_array | ||
if isinstance(symbol, pybamm.Outer): | ||
# _outer_jac defined in pybamm.Outer | ||
jac = symbol._outer_jac(left_jac, right_jac, variable) | ||
else: | ||
# _binary_jac defined in derived classes for specific rules | ||
jac = symbol._binary_jac(left_jac, right_jac) | ||
|
||
elif isinstance(symbol, pybamm.UnaryOperator): | ||
child_jac = self.jac(symbol.child, variable) | ||
# _unary_jac defined in derived classes for specific rules | ||
jac = symbol._unary_jac(child_jac) | ||
|
||
elif isinstance(symbol, pybamm.Function): | ||
children_jacs = [None] * len(symbol.children) | ||
for i, child in enumerate(symbol.children): | ||
children_jacs[i] = self.jac(child, variable) | ||
# _function_jac defined in function class | ||
jac = symbol._function_jac(children_jacs) | ||
|
||
elif isinstance(symbol, pybamm.Concatenation): | ||
children_jacs = [child.jac(variable) for child in symbol.cached_children] | ||
jac = symbol._concatenation_jac(children_jacs) | ||
|
||
else: | ||
try: | ||
jac = symbol._jac(variable) | ||
except NotImplementedError: | ||
raise NotImplementedError( | ||
"Cannot calculate Jacobian of symbol of type '{}'".format( | ||
type(symbol) | ||
) | ||
) | ||
|
||
# jacobian removes the domain(s) | ||
jac.domain = [] | ||
jac.auxiliary_domains = {} | ||
return jac | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In order to reuse already-calculated jacobians, this function needs to keep calling its own processed_left = self.jac(symbol.left)
processed_right = self.jac(symbol.right)
return symbol._jac(processed_left, processed_right) Otherwise a new There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah, thanks -- I had misunderstood how |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -441,33 +441,19 @@ def _diff(self, variable): | |
"Default behaviour for differentiation, overriden by Binary and Unary Operators" | ||
raise NotImplementedError | ||
|
||
def jac(self, variable): | ||
def jac(self, variable, known_jacs=None): | ||
""" | ||
Differentiate a symbol with respect to a (slice of) a State Vector. | ||
Default behaviour is to return `1` if differentiating with respect to | ||
yourself and zero otherwise. Binary and Unary Operators override this. | ||
Parameters | ||
---------- | ||
variable : :class:`pybamm.Symbol` | ||
The variable with respect to which to differentiate | ||
See :class:`pybamm.Jacobian`. | ||
""" | ||
if variable.id == self.id: | ||
return pybamm.Scalar(1) | ||
else: | ||
jac = self._jac(variable) | ||
# jacobian removes the domain(s) | ||
jac.domain = [] | ||
jac.auxiliary_domains = {} | ||
return jac | ||
return pybamm.Jacobian(known_jacs).jac(self, variable) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. have just seen this |
||
|
||
def _jac(self, variable): | ||
"Default behaviour for jacobian, overriden by Binary and Unary Operators" | ||
if variable.id == self.id: | ||
return pybamm.Scalar(1) | ||
else: | ||
return pybamm.Scalar(0) | ||
""" | ||
Default behaviour for jacobian, will raise a ``NotImplementedError`` | ||
if this member function has not been defined for the node. | ||
""" | ||
raise NotImplementedError | ||
|
||
def _base_evaluate(self, t=None, y=None): | ||
"""evaluate expression tree | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,7 +52,16 @@ class BaseModel(object): | |
automatically | ||
jacobian : :class:`pybamm.Concatenation` | ||
Contains the Jacobian for the model. If model.use_jacobian is True, the | ||
Jacobian is computed automatically during the set up in solve | ||
Jacobian is computed automatically during solver set up | ||
jacobian_rhs : :class:`pybamm.Concatenation` | ||
Contains the Jacobian for the part of the model which contains time derivatives. | ||
If model.use_jacobian is True, the Jacobian is computed automatically during | ||
solver set up | ||
jacobian_algebraic : :class:`pybamm.Concatenation` | ||
Contains the Jacobian for the algebraic part of the model. This may be used | ||
by the solver when calculating consistent initial conditions. If | ||
model.use_jacobian is True, the Jacobian is computed automatically during | ||
solver set up | ||
use_jacobian : bool | ||
Whether to use the Jacobian when solving the model (default is True) | ||
use_simplify : bool | ||
|
@@ -82,6 +91,7 @@ def __init__(self, name="Unnamed model"): | |
self._concatenated_initial_conditions = None | ||
self._mass_matrix = None | ||
self._jacobian = None | ||
self._jacobian_algebraic = None | ||
|
||
# Default behaviour is to use the jacobian and simplify | ||
self.use_jacobian = True | ||
|
@@ -226,6 +236,22 @@ def jacobian(self): | |
def jacobian(self, jacobian): | ||
self._jacobian = jacobian | ||
|
||
@property | ||
def jacobian_rhs(self): | ||
return self._jacobian_rhs | ||
|
||
@jacobian_rhs.setter | ||
def jacobian_rhs(self, jacobian_rhs): | ||
self._jacobian_rhs = jacobian_rhs | ||
|
||
@property | ||
def jacobian_algebraic(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why is there a jacobian_algebraic but no jacobian_rhs? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. only because we only ever use either the full jacobian (for the solver) or the algebraic part (for consistent ics). I'll add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh right, I had forgotten about that, probably fine without |
||
return self._jacobian_algebraic | ||
|
||
@jacobian_algebraic.setter | ||
def jacobian_algebraic(self, jacobian_algebraic): | ||
self._jacobian_algebraic = jacobian_algebraic | ||
|
||
@property | ||
def set_of_parameters(self): | ||
return self._set_of_parameters | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a comment here referring to this PR to explain why this function isn't used. I could see us forgetting and wondering why we don't use this in future
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also, if we make some kind of master simulation class we could use this method from the disretisation in the solver set up since we would be able to access the disc of the model when in the solver set up