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

I2304 multiple models to solver #2481

Merged
merged 30 commits into from
Dec 13, 2022
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b7e94e4
#2304 check if model has already been setup
martinjrobins Nov 16, 2022
7152598
Merge branch 'develop' into i2304-multiple-models-to-solver
martinjrobins Nov 21, 2022
178b385
#2304 raise runtimeerror on base solver if multiple models passed
martinjrobins Nov 21, 2022
f3f26c1
#2304 make sure experiment creates multiple solvers
martinjrobins Nov 21, 2022
be19129
#2304 add test for cccv simulation with idaklu and casadi solvers
martinjrobins Nov 21, 2022
4ff066e
#2304 need two cycles to test behavior
martinjrobins Nov 21, 2022
f4a0599
style: pre-commit fixes
pre-commit-ci[bot] Nov 21, 2022
06529cf
#2304 fix step so that multiple simulation solves work again
martinjrobins Nov 23, 2022
fd37869
Merge branch 'i2304-multiple-models-to-solver' of github.com:pybamm-t…
martinjrobins Nov 23, 2022
e2b5533
style: pre-commit fixes
pre-commit-ci[bot] Nov 23, 2022
3b52472
#2304 use solver.set_up for reseting initial conditions
martinjrobins Nov 23, 2022
1a973de
Merge branch 'i2304-multiple-models-to-solver' of github.com:pybamm-t…
martinjrobins Nov 23, 2022
d911b67
#2304 flake8
martinjrobins Nov 23, 2022
23603d0
#2304 add to changelog
martinjrobins Nov 23, 2022
bff61b4
#2304 fix tests
martinjrobins Nov 23, 2022
692c07e
#2304 fix integration test
martinjrobins Nov 23, 2022
c53cafc
#2304 fix integration test idaklu
martinjrobins Nov 23, 2022
a48cce9
Merge branch 'develop' into i2304-multiple-models-to-solver
martinjrobins Dec 9, 2022
319c186
#2304 update changelog
martinjrobins Dec 9, 2022
470e123
#2304 BaseSolver.models_set_up -> BaseSolver._model_set_up
martinjrobins Dec 9, 2022
482e9b3
#2304 flake8
martinjrobins Dec 9, 2022
1c161ab
#2304 propagate solver.models_set_up => solver._model_set_up
martinjrobins Dec 12, 2022
05de70c
Merge branch 'develop' into i2304-multiple-models-to-solver
martinjrobins Dec 12, 2022
17d58e7
#2304 fix some model notebooks
martinjrobins Dec 12, 2022
9a5f9c8
#2304 fix ode_solver notebook
martinjrobins Dec 12, 2022
3e35fb3
#2304 fix dae_solver notebook
martinjrobins Dec 12, 2022
cb83078
#2304 fix speed-up solver
martinjrobins Dec 12, 2022
8616fca
#2304 fix change-settings notebook
martinjrobins Dec 12, 2022
0ddf5db
#2304 fix comparing full and reduced order notebook
martinjrobins Dec 12, 2022
e24a9fc
#2304 models_setup => model_set_up
martinjrobins Dec 12, 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
- Renamed entry point for parameter sets to `pybamm_parameter_sets` ([#2475](https://github.com/pybamm-team/PyBaMM/pull/2475))
- Removed code for generating `ModelingToolkit` problems ([#2432](https://github.com/pybamm-team/PyBaMM/pull/2432))
- Removed `FirstOrder` and `Composite` lead-acid models, and some submodels specific to those models ([#2431](https://github.com/pybamm-team/PyBaMM/pull/2431))
- Trying to use a solver to solve multiple models results in a RuntimeError exception ([#2481](https://github.com/pybamm-team/PyBaMM/pull/2481))
martinjrobins marked this conversation as resolved.
Show resolved Hide resolved

# [v22.10.post1](https://github.com/pybamm-team/PyBaMM/tree/v22.10.post1) - 2022-10-31

Expand Down
1 change: 1 addition & 0 deletions casadi-headers
Submodule casadi-headers added at c31bbf
12 changes: 10 additions & 2 deletions pybamm/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,15 @@ def __init__(
self.submesh_types = submesh_types or self.model.default_submesh_types
self.var_pts = var_pts or self.model.default_var_pts
self.spatial_methods = spatial_methods or self.model.default_spatial_methods
self.solver = solver or self.model.default_solver
self._solver = solver or self.model.default_solver
self.output_variables = output_variables

# Initialize empty built states
self._model_with_set_params = None
self._built_model = None
self._built_initial_soc = None
self.op_conds_to_built_models = None
self.op_conds_to_built_solvers = None
self._mesh = None
self._disc = None
self._solution = None
Expand Down Expand Up @@ -383,6 +384,7 @@ def set_initial_soc(self, initial_soc):
self._model_with_set_params = None
self._built_model = None
self.op_conds_to_built_models = None
self.op_conds_to_built_solvers = None

c_n_init = self.parameter_values[
"Initial concentration in negative electrode [mol.m-3]"
Expand Down Expand Up @@ -439,11 +441,13 @@ def build(self, check_model=True, initial_soc=None):
self._built_model = self._disc.process_model(
self._model_with_set_params, inplace=False, check_model=check_model
)
# rebuilt model so clear solver setup
self._solver.models_set_up = {}

def build_for_experiment(self, check_model=True, initial_soc=None):
"""
Similar to :meth:`Simulation.build`, but for the case of simulating an
experiment, where there may be several models to build
experiment, where there may be several models and solvers to build.
"""
if initial_soc is not None:
self.set_initial_soc(initial_soc)
Expand All @@ -461,12 +465,15 @@ def build_for_experiment(self, check_model=True, initial_soc=None):
self._disc = pybamm.Discretisation(self._mesh, self._spatial_methods)
# Process all the different models
self.op_conds_to_built_models = {}
self.op_conds_to_built_solvers = {}
for op_cond, model_with_set_params in self.op_string_to_model.items():
# It's ok to modify the model with set parameters in place as it's
# not returned anywhere
built_model = self._disc.process_model(
model_with_set_params, inplace=True, check_model=check_model
)
solver = self.solver.copy()
self.op_conds_to_built_solvers[op_cond] = solver
self.op_conds_to_built_models[op_cond] = built_model

def solve(
Expand Down Expand Up @@ -707,6 +714,7 @@ def solve(
dt = op_conds["time"]
op_conds_str = op_conds["string"]
model = self.op_conds_to_built_models[op_conds_str]
solver = self.op_conds_to_built_solvers[op_conds_str]

logs["step number"] = (step_num, cycle_length)
logs["step operating conditions"] = op_conds_str
Expand Down
50 changes: 34 additions & 16 deletions pybamm/solvers/base_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -666,7 +666,8 @@ def solve(
----------
model : :class:`pybamm.BaseModel`
The model whose solution to calculate. Must have attributes rhs and
initial_conditions
initial_conditions. All calls to solve must pass in the same model or
an error is raised
t_eval : numeric type
The times (in seconds) at which to compute the solution
external_variables : dict
Expand Down Expand Up @@ -698,6 +699,8 @@ def solve(
:class:`pybamm.ModelError`
If an empty model is passed (`model.rhs = {}` and `model.algebraic={}` and
`model.variables = {}`)
:class:`RuntimeError`
If multiple calls to `solve` pass in different models

"""
pybamm.logger.info("Start solving {} with {}".format(model.name, self.name))
Expand Down Expand Up @@ -781,6 +784,13 @@ def solve(
# Set up (if not done already)
timer = pybamm.Timer()
if model not in self.models_set_up:
if len(self.models_set_up) > 0:
martinjrobins marked this conversation as resolved.
Show resolved Hide resolved
existing_model = next(iter(self.models_set_up))
raise RuntimeError(
"This solver has already been initialised for model "
f'"{existing_model.name}". Please create a separate '
"solver for this model"
)
# It is assumed that when len(inputs_list) > 1, model set
# up (initial condition, time-scale and length-scale) does
# not depend on input parameters. Thefore only `ext_and_inputs[0]`
Expand Down Expand Up @@ -1138,29 +1148,36 @@ def step(
# Set up external variables and inputs
ext_and_inputs = self._set_up_ext_and_inputs(model, external_variables, inputs)

if (
isinstance(old_solution, pybamm.EmptySolution)
and old_solution.termination is None
):
# Run set up on first step
pybamm.logger.verbose(
"Start stepping {} with {}".format(model.name, self.name)
)
t = old_solution.t[-1]

first_step_this_model = False
if model not in self.models_set_up:
first_step_this_model = True
if len(self.models_set_up) > 0:
existing_model = next(iter(self.models_set_up))
raise RuntimeError(
"This solver has already been initialised for model "
f'"{existing_model.name}". Please create a separate '
"solver for this model"
)
self.set_up(model, ext_and_inputs)
self.models_set_up.update(
{model: {"initial conditions": model.concatenated_initial_conditions}}
)
elif model not in self.models_set_up:
# Run set up if the model has changed

self.set_up(model, ext_and_inputs)
self.models_set_up.update(
{model: {"initial conditions": model.concatenated_initial_conditions}}
if (
isinstance(old_solution, pybamm.EmptySolution)
and old_solution.termination is None
):
pybamm.logger.verbose(
"Start stepping {} with {}".format(model.name, self.name)
)
t = old_solution.t[-1]

if not isinstance(old_solution, pybamm.EmptySolution):
if isinstance(old_solution, pybamm.EmptySolution):
if not first_step_this_model:
# reset y0 to original initial conditions
self.set_up(model, ext_and_inputs, ics_only=True)
else:
if old_solution.all_models[-1] == model:
# initialize with old solution
model.y0 = old_solution.all_ys[-1][:, -1]
Expand All @@ -1171,6 +1188,7 @@ def step(
model.y0 = concatenated_initial_conditions.evaluate(
0, inputs=ext_and_inputs
)

set_up_time = timer.time()

# (Re-)calculate consistent initial conditions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def get_max_error(current):
solution_loqs = solver.solve(
leading_order_model, t_eval, inputs={"Current function [A]": current}
)
solution_full = solver.solve(
solution_full = solver.copy().solve(
full_model, t_eval, inputs={"Current function [A]": current}
)

Expand Down
2 changes: 1 addition & 1 deletion tests/integration/test_solvers/test_idaklu.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ def test_set_tol_by_variable(self):

def test_changing_grid(self):
model = pybamm.lithium_ion.SPM()
solver = pybamm.IDAKLUSolver()

# load parameter values and geometry
geometry = model.default_geometry
Expand All @@ -103,6 +102,7 @@ def test_changing_grid(self):
mesh = pybamm.Mesh(geometry, model.default_submesh_types, var_pts)
disc = pybamm.Discretisation(mesh, model.default_spatial_methods)
model_disc = disc.process_model(model, inplace=False)
solver = pybamm.IDAKLUSolver()

# solve
solver.solve(model_disc, t_eval)
Expand Down
33 changes: 33 additions & 0 deletions tests/unit/test_experiments/test_simulation_with_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,39 @@ def test_run_experiment_cccv_ode(self):
)
self.assertEqual(solutions[1].termination, "final time")

@unittest.skipIf(not pybamm.have_idaklu(), "idaklu solver is not installed")
def test_run_experiment_cccv_solvers(self):
experiment_2step = pybamm.Experiment(
[
(
"Discharge at C/20 for 1 hour",
"Charge at 1 A until 4.1 V",
"Hold at 4.1 V until C/2",
"Discharge at 2 W for 1 hour",
),
]
* 2,
)

solutions = []
for solver in [pybamm.CasadiSolver(), pybamm.IDAKLUSolver()]:
model = pybamm.lithium_ion.SPM()
sim = pybamm.Simulation(model, experiment=experiment_2step, solver=solver)
solution = sim.solve()
solutions.append(solution)

np.testing.assert_array_almost_equal(
solutions[0]["Terminal voltage [V]"].data,
solutions[1]["Terminal voltage [V]"].data,
decimal=1,
)
np.testing.assert_array_almost_equal(
solutions[0]["Current [A]"].data,
solutions[1]["Current [A]"].data,
decimal=0,
)
self.assertEqual(solutions[1].termination, "final time")

def test_run_experiment_drive_cycle(self):
drive_cycle = np.array([np.arange(10), np.arange(10)]).T
experiment = pybamm.Experiment(
Expand Down
7 changes: 4 additions & 3 deletions tests/unit/test_plotting/test_quick_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,14 +475,15 @@ def test_model_with_inputs(self):
timescale = parameter_values.evaluate(pybamm.LithiumIonParameters().timescale)
model = pybamm.lithium_ion.SPMe({"timescale": timescale})
parameter_values.update({"Electrode height [m]": "[input]"})
solver = pybamm.CasadiSolver(mode="safe")
solver1 = pybamm.CasadiSolver(mode="safe")
sim1 = pybamm.Simulation(
model, parameter_values=parameter_values, solver=solver
model, parameter_values=parameter_values, solver=solver1
)
inputs1 = {"Electrode height [m]": 1.00}
sol1 = sim1.solve(t_eval=np.linspace(0, 1000, 101), inputs=inputs1)
solver2 = pybamm.CasadiSolver(mode="safe")
sim2 = pybamm.Simulation(
model, parameter_values=parameter_values, solver=solver
model, parameter_values=parameter_values, solver=solver2
)
inputs2 = {"Electrode height [m]": 2.00}
sol2 = sim2.solve(t_eval=np.linspace(0, 1000, 101), inputs=inputs2)
Expand Down
15 changes: 15 additions & 0 deletions tests/unit/test_solvers/test_base_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,21 @@ def test_extrapolation_warnings(self):
with self.assertWarns(pybamm.SolverWarning):
solver.solve(model, t_eval=[0, 1])

def test_multiple_models_error(self):
model = pybamm.BaseModel()
v = pybamm.Variable("v")
model.rhs = {v: -1}
model.initial_conditions = {v: 1}
model2 = pybamm.BaseModel()
v2 = pybamm.Variable("v")
model2.rhs = {v2: -1}
model2.initial_conditions = {v2: 1}

solver = pybamm.ScipySolver()
solver.solve(model, t_eval=[0, 1])
with self.assertRaisesRegex(RuntimeError, "already been initialised"):
solver.solve(model2, t_eval=[0, 1])

@unittest.skipIf(not pybamm.have_idaklu(), "idaklu solver is not installed")
def test_sensitivities(self):
def exact_diff_a(y, a, b):
Expand Down
3 changes: 3 additions & 0 deletions tests/unit/test_solvers/test_casadi_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ def test_model_solver_failure(self):
solution = solver.solve(model_disc, t_eval)
self.assertLess(solution.t[-1], 20)
# Solve with failure at t=0
solver = pybamm.CasadiSolver(
dt_max=1e-3, return_solution_if_failed_early=True, max_step_decrease_count=2
)
model.initial_conditions = {var: 0, var2: 1}
model_disc = disc.process_model(model, inplace=False)
t_eval = np.linspace(0, 20, 100)
Expand Down
10 changes: 4 additions & 6 deletions tests/unit/test_solvers/test_scipy_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,12 +221,9 @@ def test_step_different_model(self):
np.testing.assert_array_equal(step_sol1.t, [0, dt])
np.testing.assert_array_almost_equal(step_sol1.y[0], np.exp(0.1 * step_sol1.t))

# Step again, the model has changed
step_sol2 = solver.step(step_sol1, model2, dt)
np.testing.assert_array_equal(step_sol2.t, [0, dt, 2 * dt])
np.testing.assert_array_almost_equal(
step_sol2.all_ys[0][0], np.exp(0.1 * step_sol1.t)
)
# Step again, the model has changed so this raises an error
with self.assertRaisesRegex(RuntimeError, "already been initialised"):
solver.step(step_sol1, model2, dt)

def test_model_solver_with_inputs(self):
# Create model
Expand Down Expand Up @@ -496,6 +493,7 @@ def test_model_solver_manually_update_initial_conditions(self):
model.concatenated_initial_conditions = pybamm.NumpyConcatenation(
pybamm.Vector([[2]])
)
solver = pybamm.ScipySolver(rtol=1e-8, atol=1e-8)
solution = solver.solve(model, t_eval)
np.testing.assert_array_almost_equal(
solution.y[0], 2 * np.exp(-solution.t), decimal=5
Expand Down