-
-
Notifications
You must be signed in to change notification settings - Fork 553
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
Conversation
Codecov Report
@@ Coverage Diff @@
## master #670 +/- ##
==========================================
- Coverage 98.59% 98.56% -0.03%
==========================================
Files 169 170 +1
Lines 8450 8517 +67
==========================================
+ Hits 8331 8395 +64
- Misses 119 122 +3
Continue to review full report at Codecov.
|
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.
Cheers Rob, looks like you found a few bugs that will make this more efficient. There are a few things to address, especially with the Jacobian class, see below
In order to reuse already-calculated jacobians, this function needs to keep calling its own jac
method. For example, for a binary operator, call Jacobian.jac
on left and right, and then call symbol._jac
on processed_left and processed_right:
processed_left = self.jac(symbol.left)
processed_right = self.jac(symbol.right)
return symbol._jac(processed_left, processed_right)
Otherwise a new Jacobian
object just gets created each time. See the Simplify
class for example.
Apologies if you're doing this in a different way that I've missed.
"""Creates Jacobian of the discretised model. | ||
Note that the model is assumed to be of the form M*y_dot = f(t,y), where | ||
M is the (possibly singular) mass matrix. The Jacobian is df/dy. | ||
|
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
@@ -696,8 +754,15 @@ def _concatenate_in_order(self, var_eqn_dict, check_complete=False): | |||
---------- | |||
var_eqn_dict : dict | |||
Equations ({variable: equation} dict) to dicretise | |||
check_complete : bool, optional | |||
Whether to check keys in var_eqn_dict against self.y_slices. Defualt |
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.
got some defualt sneaking in again 👀
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.
I have muscle memory for this typo...
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.
haha I assumed it had got into your autocorrect
if shape == (): | ||
return pybamm.Scalar(0) | ||
else: | ||
if len(shape) == 1 or shape[1] == 1: | ||
return pybamm.Vector(np.zeros(shape)) | ||
else: | ||
return pybamm.Matrix(csr_matrix(shape)) |
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.
seeing them all together in review mode, these lines appear several times together and could be abstracted into a functon pybamm.zeros_of_shape(shape)
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.
good suggestion
# 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 comment
The 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 jac
method. For example, for a binary operator, call Jacobian.jac
on left and right, and then call symbol._jac
on processed_left and processed_right:
processed_left = self.jac(symbol.left)
processed_right = self.jac(symbol.right)
return symbol._jac(processed_left, processed_right)
Otherwise a new Jacobian
object just gets created each time. See the Simplify
class for example.
Apologies if you're doing this in a different way that I've missed.
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.
ah, thanks -- I had misunderstood how known_jacs
was getting passed around.
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 comment
The reason will be displayed to describe this comment to others. Learn more.
have just seen this known_jacs
thing. I still think it's more efficient to only create the Jacobian
class once
pybamm/models/base_model.py
Outdated
@@ -52,7 +52,12 @@ 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 discrisation |
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.
Jacobian is computed automatically during discrisation | |
Jacobian is computed automatically during discretisation |
pybamm/models/base_model.py
Outdated
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 | ||
discrisation |
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.
discrisation | |
discretisation |
@@ -226,6 +232,14 @@ def jacobian(self): | |||
def jacobian(self, jacobian): | |||
self._jacobian = jacobian | |||
|
|||
@property | |||
def jacobian_algebraic(self): |
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.
why is there a jacobian_algebraic but no jacobian_rhs?
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.
only because we only ever use either the full jacobian (for the solver) or the algebraic part (for consistent ics). I'll add jacobian_rhs
too for completeness.
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.
oh right, I had forgotten about that, probably fine without jacobian_rhs
then. Need to make sure we aren't calculating the jacobian for the algebraic part twice then (can't remember if already doing this)
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.
Looks great now @rtimms !
pybamm/expression_tree/jacobian.py
Outdated
left_jac = self.jac(left, variable) | ||
right_jac = self.jac(right, variable) | ||
# _outer_jac defined in pybamm.Outer | ||
jac = symbol._outer_jac(left_jac, right_jac, variable) |
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.
not a big deal but you could have the isinstance(Outer) check on line 60 to avoid writing the process_children lines twice
@@ -80,9 +80,9 @@ def test_linear(self): | |||
|
|||
# test jac of outer if left evaluates to number | |||
func = pybamm.Outer(pybamm.Scalar(1), pybamm.Scalar(4)) | |||
jacobian = np.zeros((1, 4)) | |||
# jacobian = np.zeros((1, 4)) |
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.
can be removed
Description
Adds a class
Jacobian
to re-use know jacobians of any expressions. Also changes the behavior of some binary simplifies which were returning dense matrices of zeros in some cases.I was going to do Jacobian by equation as part of this (#651) but (I think) that needs to be done in discretisation so you can use
_concatenate_in_order
. I've added a methoddisc.create_jacobian
which lets you do compute the jacobian, but by default I've left calculation of jac until the solve. The main reason being that it is much faster to compute the jacobian on the simplified model, but you dont want to simplify as part of discretisation as this prevents you from updating parameters. I've kept thecreate_jacobian
method as there may be use cases where you want to compute the jacobian (once) of the unsimplified model, and then go ahead and solve multiple times but with different params etc.Fixes #665
Type of change
Please add a line in the relevant section of CHANGELOG.md to document the change (include PR #) - note reverse order of PR #s.
Key checklist:
$ flake8
$ python run-tests.py --unit
$ cd docs
and then$ make clean; make html
You can run all three at once, using
$ python run-tests.py --quick
.Further checks: