Skip to content
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

I1863 idklu casadi #2002

Merged
merged 36 commits into from
Apr 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
6910821
#1863 started a casadi version of the idaklu solver
martinjrobins Mar 16, 2022
aa3b66a
#1863 finish draft of casadi solver, and restructure cpp code
martinjrobins Mar 17, 2022
14784fc
#1863 idaklu_casadi cpp code compiles
martinjrobins Mar 17, 2022
dbd8de5
#1863 restructuring idklu_solver.py for new casadi solver, need to ge…
martinjrobins Mar 17, 2022
ff51fb2
#1863 get base_solver to calculate jac action
martinjrobins Mar 18, 2022
61e5806
#1863 using a serialisation to string to transfer casadi functions
martinjrobins Mar 18, 2022
be0e85f
#1863 unit tests for ikdlu solver works
martinjrobins Mar 21, 2022
483ec8d
#1863 get some solver stats from sundials
martinjrobins Mar 23, 2022
c26fcb1
#1863 restructuring idklu solver setup to set_up
martinjrobins Mar 24, 2022
0d53292
#1863 need to pass inputs to c++ idklu functions
martinjrobins Mar 24, 2022
9092b83
#1863 idklu setup working ok now, inputs passed down to c++
martinjrobins Mar 24, 2022
b131be4
#1863 calcIC is faster with idklu
martinjrobins Mar 24, 2022
2b39a67
#1863 fix all idklu tests
martinjrobins Mar 31, 2022
3259c2b
#1863 fix tests
martinjrobins Mar 31, 2022
00a2e68
#1863 add casadi headers to CI, and add some installation docs
martinjrobins Mar 31, 2022
9b6ac61
#1863 make CasadiFunction constructor explicit
martinjrobins Mar 31, 2022
8fb1910
Merge branch 'develop' into i1863-idklu-casadi
martinjrobins Mar 31, 2022
77f2f26
#1863 add to CHANGELOG.md
martinjrobins Mar 31, 2022
fc4a458
#1863 try and work out why idklusolver not working in CI
martinjrobins Apr 1, 2022
fee37fc
#1863 use CMake to find casadi library on system
martinjrobins Apr 1, 2022
259adf0
#1863 done use the cxx11 namespace for std::string to match casadi
martinjrobins Apr 4, 2022
572dfbe
#1863 fix have_jax bug
martinjrobins Apr 4, 2022
5b23522
#1863 fix spme sens integration test
martinjrobins Apr 4, 2022
48254ea
#1863 coverage
martinjrobins Apr 4, 2022
b0bd5bb
#1863 add test for jax jvp
martinjrobins Apr 5, 2022
71ce5e6
#1863 #2008 use InputParameter.create_copy in Discretisation._process…
martinjrobins Apr 5, 2022
603cc6a
#1863 #2008 fix jac function returning incorrect size zero matrix for…
martinjrobins Apr 5, 2022
6bf2ba7
#1863 fix concatentation of input param vectors
martinjrobins Apr 5, 2022
9a46550
#1863 #2008 fix size calculation of input_parameter
martinjrobins Apr 5, 2022
f11ef1c
#1863 fix input_parameter expected size if no domain
martinjrobins Apr 5, 2022
f3f9a94
#1863 #2008 fix code smell
martinjrobins Apr 5, 2022
55b33bb
'#1863 #2008 fix input_parameter size bug again
martinjrobins Apr 5, 2022
49b2283
#1863 #2008 only override input_parameter expected size if it is None
martinjrobins Apr 5, 2022
d10af3a
#1863 coverage
martinjrobins Apr 6, 2022
39719ff
#1863 add docstring for BaseSolver.process
martinjrobins Apr 20, 2022
e01bb98
Merge branch 'develop' into i1863-idklu-casadi
martinjrobins Apr 20, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Features

- Added a casadi version of the IDKLU solver, which is used for `model.convert_to_format = "casadi"` ([#2002](https://github.com/pybamm-team/PyBaMM/pull/2002))

## Bug fixes

- Remove old deprecation errors, including those in `parameter_values.py` that caused the simulation if, for example, the reaction rate is re-introduced manually ([#2022](https://github.com/pybamm-team/PyBaMM/pull/2022))
Expand Down
30 changes: 27 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,45 @@ endif()

project(idaklu)

set (CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS 1)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)

# casadi seems to compile without the newer versions of std::string
add_compile_definitions(_GLIBCXX_USE_CXX11_ABI=0)

if(NOT PYBIND11_DIR)
set(PYBIND11_DIR pybind11)
endif()

add_subdirectory(${PYBIND11_DIR})
pybind11_add_module(idaklu pybamm/solvers/c_solvers/idaklu.cpp)
pybind11_add_module(idaklu
pybamm/solvers/c_solvers/idaklu_python.cpp
pybamm/solvers/c_solvers/idaklu_python.hpp
pybamm/solvers/c_solvers/idaklu.cpp
pybamm/solvers/c_solvers/idaklu.hpp
pybamm/solvers/c_solvers/idaklu_casadi.cpp
pybamm/solvers/c_solvers/idaklu_casadi.hpp
pybamm/solvers/c_solvers/solution.cpp
pybamm/solvers/c_solvers/solution.hpp
)

if(NOT CASADI_DIR)
execute_process(
COMMAND "${PYTHON_EXECUTABLE}" -c
"import casadi as _; print(_.__path__[0])"
OUTPUT_VARIABLE CASADI_DIR
OUTPUT_STRIP_TRAILING_WHITESPACE)
endif()
find_package(casadi CONFIG PATHS ${CASADI_DIR} REQUIRED)

set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR})
# Sundials
find_package(SUNDIALS)
target_include_directories(idaklu PRIVATE ${SUNDIALS_INCLUDE_DIR})
target_link_libraries(idaklu PRIVATE ${SUNDIALS_LIBRARIES})
target_link_libraries(idaklu PRIVATE ${SUNDIALS_LIBRARIES} casadi)

# link suitesparse
# if using vcpkg, use config mode to
Expand Down
3 changes: 2 additions & 1 deletion docs/install/install-from-source.rst
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ If you are using MacOS, an alternative to the above is to get the required SUNDI

brew install sundials

Next, clone the pybind11 repository:
Next, clone the pybind11 and casadi-headers repositories:

.. code:: bash

Expand All @@ -103,6 +103,7 @@ If you'd rather do things yourself,
2. Compile and install SuiteSparse (PyBaMM only requires the ``KLU`` component).
3. Compile and install SUNDIALS.
4. Clone the pybind11 repository in the ``PyBaMM/`` directory (make sure the directory is named ``pybind11``).


PyBaMM ships with a python script that automates points 2. and 3. You can run it with

Expand Down
15 changes: 7 additions & 8 deletions pybamm/discretisations/discretisation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1036,14 +1036,13 @@ def _process_symbol(self, symbol):
return new_symbol

elif isinstance(symbol, pybamm.InputParameter):
# Return a new copy of the input parameter, but set the expected size
# according to the domain of the input parameter
expected_size = self._get_variable_size(symbol)
new_input_parameter = pybamm.InputParameter(
symbol.name, symbol.domain, expected_size
)
return new_input_parameter

if symbol.domain != []:
expected_size = self._get_variable_size(symbol)
else:
expected_size = None
if symbol._expected_size is None:
symbol._expected_size = expected_size
return symbol.create_copy()
else:
# Backup option: return the object
return symbol
Expand Down
12 changes: 11 additions & 1 deletion pybamm/expression_tree/input_parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#
import numbers
import numpy as np
import scipy.sparse
import pybamm


Expand Down Expand Up @@ -55,7 +56,16 @@ def _evaluate_for_shape(self):

def _jac(self, variable):
"""See :meth:`pybamm.Symbol._jac()`."""
return pybamm.Scalar(0)
n_variable = variable.evaluation_array.count(True)
nan_vector = self._evaluate_for_shape()
if isinstance(nan_vector, numbers.Number):
n_self = 1
else:
n_self = nan_vector.shape[0]
zero_matrix = scipy.sparse.csr_matrix(
(n_self, n_variable)
)
return pybamm.Matrix(zero_matrix)

def _base_evaluate(self, t=None, y=None, y_dot=None, inputs=None):
# inputs should be a dictionary
Expand Down
27 changes: 25 additions & 2 deletions pybamm/expression_tree/operations/evaluate_python.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ def find_symbols(symbol, constant_symbols, variable_symbols, output_jax=False):
symbol_str = "t"

elif isinstance(symbol, pybamm.InputParameter):
symbol_str = "inputs['{}']".format(symbol.name)
symbol_str = 'inputs["{}"]'.format(symbol.name)

else:
raise NotImplementedError(
Expand Down Expand Up @@ -621,6 +621,9 @@ def get_jacobian(self):

return EvaluatorJaxJacobian(self._jac_evaluate, self._constants)

def get_jacobian_action(self):
return self.jvp

def get_sensitivities(self):
n = len(self._arg_list)

Expand Down Expand Up @@ -662,6 +665,22 @@ def __call__(self, t=None, y=None, inputs=None):

return result

def jvp(self, t=None, y=None, v=None, inputs=None):
"""
evaluate jacobian vector product of function
"""

# generated code assumes y is a column vector
if y is not None and y.ndim == 1:
y = y.reshape(-1, 1)
if v is not None and v.ndim == 1:
v = v.reshape(-1, 1)

def bind_t_and_inputs(the_y):
return self._jit_evaluate(*self._constants, t, the_y, inputs)

return jax.jvp(bind_t_and_inputs, (y,), (v,))[1]


class EvaluatorJaxJacobian:
def __init__(self, jac_evaluate, constants):
Expand Down Expand Up @@ -690,13 +709,17 @@ def __init__(self, jac_evaluate, constants):

def __call__(self, t=None, y=None, inputs=None):
"""
Acts as a drop-in replacement for :func:`pybamm.Symbol.evaluate`
evaluate function
"""
# generated code assumes y is a column vector
if y is not None and y.ndim == 1:
y = y.reshape(-1, 1)

# execute code
result = self._jac_evaluate(*self._constants, t, y, inputs)
result = {
key: value.reshape(value.shape[0], -1)
for key, value in result.items()
}

return result
97 changes: 78 additions & 19 deletions pybamm/solvers/base_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,43 @@ def set_up(self, model, inputs=None, t_eval=None, ics_only=False):
)

def process(symbol, name, use_jacobian=None):
"""
Parameters
----------
symbol: :class:`pybamm.Symbol`
expression tree to convert
name: str
function evaluators created will have this base name
use_jacobian: bool, optional
wether to return jacobian functions

Returns
-------
func: :class:`pybamm.EvaluatorPython` or
:class:`pybamm.EvaluatorJax` or
:class:`casadi.Function`
evaluator for the function $f(y, t, p)$ given by `symbol`

jac: :class:`pybamm.EvaluatorPython` or
:class:`pybamm.EvaluatorJaxJacobian` or
:class:`casadi.Function`
evaluator for the jacobian $\frac{\partial f}{\partial y}$
of the function given by `symbol`

jacp: :class:`pybamm.EvaluatorPython` or
:class:`pybamm.EvaluatorJaxSensitivities` or
:class:`casadi.Function`
evaluator for the parameter sensitivities
$\frac{\partial f}{\partial p}$
of the function given by `symbol`

jac_action: :class:`pybamm.EvaluatorPython` or
:class:`pybamm.EvaluatorJax` or
:class:`casadi.Function`
evaluator for product of the jacobian with a vector $v$,
i.e. $\frac{\partial f}{\partial y} * v$
"""

def report(string):
# don't log event conversion
if "event" not in string:
Expand All @@ -320,8 +357,10 @@ def report(string):
if use_jacobian:
report(f"Calculating jacobian for {name} using jax")
jac = func.get_jacobian()
jac_action = func.get_jacobian_action()
else:
jac = None
jac_action = None

elif model.convert_to_format != "casadi":
# Process with pybamm functions, converting
Expand Down Expand Up @@ -356,11 +395,13 @@ def jacp(*args, **kwargs):
if use_jacobian:
report(f"Calculating jacobian for {name}")
jac = jacobian.jac(symbol, y)
if model.convert_to_format == "python":
report(f"Converting jacobian for {name} to python")
jac = pybamm.EvaluatorPython(jac)
report(f"Converting jacobian for {name} to python")
jac = pybamm.EvaluatorPython(jac)
# cannot do jacobian action efficiently for now
jac_action = None
else:
jac = None
jac_action = None

report(f"Converting {name} to python")
func = pybamm.EvaluatorPython(symbol)
Expand Down Expand Up @@ -444,32 +485,44 @@ def jacp(*args, **kwargs):
"CasADi"
)
)
jacp_dict = {}
for pname in model.calculate_sensitivities:
p_diff = casadi.jacobian(casadi_expression, p_casadi[pname])
jacp_dict[pname] = casadi.Function(
name, [t_casadi, y_and_S, p_casadi_stacked], [p_diff]
)

# jacp should be a casadi_expressiontion that returns
# a dict of sensitivities
def jacp(*args, **kwargs):
return {k: v(*args, **kwargs) for k, v in jacp_dict.items()}
# WARNING, jacp for convert_to_format=casadi does not return a dict
# instead it returns multiple return values, one for each param
# TODO: would it be faster to do the jacobian wrt pS_casadi_stacked?
jacp = casadi.Function(
name + "_jacp", [t_casadi, y_and_S, p_casadi_stacked], [
casadi.densify(
casadi.jacobian(casadi_expression, p_casadi[pname])
)
for pname in model.calculate_sensitivities
]
)

if use_jacobian:
report(f"Calculating jacobian for {name} using CasADi")
jac_casadi = casadi.jacobian(casadi_expression, y_and_S)
jac = casadi.Function(
name, [t_casadi, y_and_S, p_casadi_stacked], [jac_casadi]
name + "_jac", [t_casadi, y_and_S, p_casadi_stacked],
[jac_casadi]
)

v = casadi.MX.sym("v", model.len_rhs_and_alg + model.len_rhs_sens +
model.len_alg_sens)
jac_action_casadi = casadi.densify(
casadi.jtimes(casadi_expression, y_and_S, v)
)
jac_action = casadi.Function(
name + "_jac_action", [t_casadi, y_and_S, p_casadi_stacked, v],
[jac_action_casadi]
)
else:
jac = None
jac_action = None

func = casadi.Function(
name, [t_casadi, y_and_S, p_casadi_stacked], [casadi_expression]
)

return func, jac, jacp
return func, jac, jacp, jac_action
martinjrobins marked this conversation as resolved.
Show resolved Hide resolved

# Process initial conditions
initial_conditions = process(
Expand Down Expand Up @@ -558,9 +611,9 @@ def jacp(*args, **kwargs):

# Process rhs, algebraic, residual and event expressions
# and wrap in callables
rhs, jac_rhs, jacp_rhs = process(model.concatenated_rhs, "RHS")
rhs, jac_rhs, jacp_rhs, jac_rhs_action = process(model.concatenated_rhs, "RHS")

algebraic, jac_algebraic, jacp_algebraic = process(
algebraic, jac_algebraic, jacp_algebraic, jac_algebraic_action = process(
model.concatenated_algebraic, "algebraic"
)

Expand All @@ -573,7 +626,10 @@ def jacp(*args, **kwargs):
rhs_algebraic = pybamm.NumpyConcatenation(
model.concatenated_rhs, model.concatenated_algebraic
)
rhs_algebraic, jac_rhs_algebraic, jacp_rhs_algebraic = process(

(rhs_algebraic,
jac_rhs_algebraic,
jacp_rhs_algebraic, jac_rhs_algebraic_action) = process(
rhs_algebraic, "rhs_algebraic"
)

Expand Down Expand Up @@ -633,12 +689,15 @@ def jacp(*args, **kwargs):
model.interpolant_extrapolation_events_eval = interpolant_extrapolation_events

model.jac_rhs_eval = jac_rhs
model.jac_rhs_action_eval = jac_rhs_action
model.jacp_rhs_eval = jacp_rhs

model.jac_algebraic_eval = jac_algebraic
model.jac_algebraic_action_eval = jac_algebraic_action
model.jacp_algebraic_eval = jacp_algebraic

model.jac_rhs_algebraic_eval = jac_rhs_algebraic
model.jac_rhs_algebraic_action_eval = jac_rhs_algebraic_action
model.jacp_rhs_algebraic_eval = jacp_rhs_algebraic

# Save CasADi functions for the CasADi solver
Expand Down
Loading