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

Issue 835 initial conditions #839

Merged
merged 9 commits into from
Feb 19, 2020
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@

## Bug fixes

- Time for solver should now be given in seconds ([#832](https://github.com/pybamm-team/PyBaMM/pull/832))
- Moved evaluation of initial conditions to solver ([#839](https://github.com/pybamm-team/PyBaMM/pull/839))
- Fixed a bug where the first line of the data wasn't loaded when parameters are loaded from data ([#819](https://github.com/pybamm-team/PyBaMM/pull/819))
- Made `graphviz` an optional dependency ([#810](https://github.com/pybamm-team/PyBaMM/pull/810))
- Fixed examples to run with basic pip installation ([#800](https://github.com/pybamm-team/PyBaMM/pull/800))
Expand All @@ -74,6 +74,7 @@

## Breaking changes

- Time for solver should now be given in seconds ([#832](https://github.com/pybamm-team/PyBaMM/pull/832))
- Model events are now represented as a list of `pybamm.Event` ([#759](https://github.com/pybamm-team/PyBaMM/issues/759)
- Removed `ParameterValues.update_model`, whose functionality is now replaced by `InputParameter` ([#801](https://github.com/pybamm-team/PyBaMM/pull/801))
- Removed `Outer` and `Kron` nodes as no longer used ([#777](https://github.com/pybamm-team/PyBaMM/pull/777))
Expand Down
8 changes: 4 additions & 4 deletions examples/notebooks/solvers/dae-solver.ipynb

Large diffs are not rendered by default.

20 changes: 10 additions & 10 deletions examples/notebooks/spatial_methods/finite-volumes.ipynb

Large diffs are not rendered by default.

15 changes: 8 additions & 7 deletions pybamm/discretisations/discretisation.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,7 @@ def process_model(self, model, inplace=True, check_model=True):
for event in model.events:
pybamm.logger.debug("Discretise event '{}'".format(event.name))
processed_event = pybamm.Event(
event.name,
self.process_symbol(event.expression),
event.event_type
event.name, self.process_symbol(event.expression), event.event_type
)
processed_events.append(processed_event)
model_disc.events = processed_events
Expand Down Expand Up @@ -402,7 +400,7 @@ def process_initial_conditions(self, model):
# check that all initial conditions are set
processed_concatenated_initial_conditions = self._concatenate_in_order(
processed_initial_conditions, check_complete=True
).evaluate(0, None)
)

return processed_initial_conditions, processed_concatenated_initial_conditions

Expand Down Expand Up @@ -996,17 +994,20 @@ def check_initial_conditions(self, model):
"""Check initial conditions are a numpy array"""
# Individual
for var, eqn in model.initial_conditions.items():
assert type(eqn.evaluate(0, None)) is np.ndarray, pybamm.ModelError(
assert isinstance(
eqn.evaluate(t=0, u="shape test"), np.ndarray
), pybamm.ModelError(
"""
initial_conditions must be numpy array after discretisation but they are
{} for variable '{}'.
""".format(
type(eqn.evaluate(0, None)), var
type(eqn.evaluate(t=0, u="shape test")), var
)
)
# Concatenated
assert (
type(model.concatenated_initial_conditions) is np.ndarray
type(model.concatenated_initial_conditions.evaluate(t=0, u="shape test"))
is np.ndarray
), pybamm.ModelError(
"""
Concatenated initial_conditions must be numpy array after discretisation but
Expand Down
28 changes: 9 additions & 19 deletions pybamm/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -474,25 +474,15 @@ def step(
if solver is None:
solver = self.solver

if save is False:
# Don't pass previous solution
self._solution = solver.step(
None,
self.built_model,
dt,
npts=npts,
external_variables=external_variables,
inputs=inputs,
)
else:
self._solution = solver.step(
self._solution,
self.built_model,
dt,
npts=npts,
external_variables=external_variables,
inputs=inputs,
)
self._solution = solver.step(
self._solution,
self.built_model,
dt,
npts=npts,
external_variables=external_variables,
inputs=inputs,
save=save,
)

def get_variable_array(self, *variables):
"""
Expand Down
2 changes: 1 addition & 1 deletion pybamm/solvers/algebraic_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def jacobian(y):
jacobian = None

# Use "initial conditions" set in model as initial guess
y0_guess = model.concatenated_initial_conditions
y0_guess = model.concatenated_initial_conditions.evaluate(t=0)

# Solve
solve_start_time = timer.time()
Expand Down
41 changes: 22 additions & 19 deletions pybamm/solvers/base_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ def __init__(
self.max_steps = max_steps

self.models_set_up = set()
self.model_step_times = {}

# Defaults, can be overwritten by specific solver
self.name = "Base solver"
Expand Down Expand Up @@ -115,7 +114,7 @@ def set_up(self, model, inputs=None):

"""
inputs = inputs or {}
y0 = model.concatenated_initial_conditions
y0 = model.concatenated_initial_conditions.evaluate(0, None, inputs)

# Set model timescale
model.timescale_eval = model.timescale.evaluate(u=inputs)
Expand All @@ -138,9 +137,7 @@ def set_up(self, model, inputs=None):
if model.convert_to_format != "casadi":
simp = pybamm.Simplification()
# Create Jacobian from concatenated rhs and algebraic
y = pybamm.StateVector(
slice(0, np.size(model.concatenated_initial_conditions))
)
y = pybamm.StateVector(slice(0, np.size(y0)))
# set up Jacobian object, for re-use of dict
jacobian = pybamm.Jacobian()
else:
Expand Down Expand Up @@ -280,13 +277,13 @@ def report(string):
residuals, residuals_eval, jacobian_eval = process(all_states, "residuals")
model.residuals_eval = residuals_eval
model.jacobian_eval = jacobian_eval
y0_guess = model.concatenated_initial_conditions.flatten()
y0_guess = y0.flatten()
model.y0 = self.calculate_consistent_state(model, 0, y0_guess)
else:
# can use DAE solver to solve ODE model
model.residuals_eval = Residuals(rhs, "residuals", model)
model.jacobian_eval = jac_rhs
model.y0 = model.concatenated_initial_conditions[:, 0]
model.y0 = y0.flatten()

# Save CasADi functions for the CasADi solver
# Note: when we pass to casadi the ode part of the problem must be in explicit
Expand Down Expand Up @@ -564,7 +561,7 @@ def solve(self, model, t_eval, external_variables=None, inputs=None):

# Add model and inputs to solution
solution.model = model
solution.inputs = inputs
solution.inputs = ext_and_inputs

# Identify the event that caused termination
termination = self.get_termination_reason(solution, model.events)
Expand All @@ -580,7 +577,14 @@ def solve(self, model, t_eval, external_variables=None, inputs=None):
return solution

def step(
self, old_solution, model, dt, npts=2, external_variables=None, inputs=None
self,
old_solution,
model,
dt,
npts=2,
external_variables=None,
inputs=None,
save=True,
):
"""
Step the solution of the model forward by a given time increment. The
Expand All @@ -604,7 +608,8 @@ def step(
values at the current time
inputs : dict, optional
Any input parameters to pass to the model when solving

save : bool
Turn on to store the solution of all previous timesteps

Raises
------
Expand Down Expand Up @@ -634,20 +639,22 @@ def step(
ext_and_inputs = {**external_variables, **inputs}

# Run set up on first step
if model not in self.model_step_times:
if old_solution is None:
pybamm.logger.info(
"Start stepping {} with {}".format(model.name, self.name)
)
self.set_up(model, ext_and_inputs)
self.model_step_times[model] = 0.0
t = 0.0
set_up_time = timer.time()
else:
# initialize with old solution
t = old_solution.t[-1]
model.y0 = old_solution.y[:, -1]
set_up_time = 0

# Non-dimensionalise dt
dt_dimensionless = dt / model.timescale_eval
# Step
t = self.model_step_times[model]
t_eval = np.linspace(t, t + dt_dimensionless, npts)
# Set inputs and external
self.set_inputs(model, ext_and_inputs)
Expand All @@ -662,15 +669,11 @@ def step(

# Add model and inputs to solution
solution.model = model
solution.inputs = inputs
solution.inputs = ext_and_inputs

# Identify the event that caused termination
termination = self.get_termination_reason(solution, model.events)

# Set self.t and self.y0 to their values at the final step
self.model_step_times[model] = solution.t[-1]
model.y0 = solution.y[:, -1]

pybamm.logger.debug("Finish stepping {} ({})".format(model.name, termination))
if set_up_time:
pybamm.logger.debug(
Expand All @@ -684,7 +687,7 @@ def step(
pybamm.logger.debug(
"Step time: {}".format(timer.format(solution.solve_time))
)
if old_solution is None:
if save is False or old_solution is None:
return solution
else:
return old_solution + solution
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/test_models/standard_model_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def evaluate_model(self, simplify=False, use_known_evals=False, to_python=False)
if simplify:
eqn = eqn.simplify()

y = self.model.concatenated_initial_conditions
y = self.model.concatenated_initial_conditions.evaluate(t=0)
if use_known_evals:
eqn_eval, known_evals = eqn.evaluate(0, y, known_evals={})
elif to_python:
Expand Down
65 changes: 65 additions & 0 deletions tests/integration/test_solvers/test_external_variables.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#
# Test solvers with external variables
#
import pybamm
import numpy as np
import unittest


class TestExternalVariables(unittest.TestCase):
def test_on_dfn(self):
e_height = 0.25

model = pybamm.lithium_ion.DFN()
geometry = model.default_geometry
param = model.default_parameter_values
param.update({"Electrode height [m]": "[input]"})
param.process_model(model)
param.process_geometry(geometry)
inputs = {"Electrode height [m]": e_height}
var = pybamm.standard_spatial_vars
var_pts = {var.x_n: 5, var.x_s: 5, var.x_p: 5, var.r_n: 10, var.r_p: 10}
spatial_methods = model.default_spatial_methods

solver = pybamm.CasadiSolver()
sim = pybamm.Simulation(
model=model,
geometry=geometry,
parameter_values=param,
var_pts=var_pts,
spatial_methods=spatial_methods,
solver=solver,
)
sim.solve(t_eval=np.linspace(0, 3600, 100), inputs=inputs)

def test_external_variables_SPMe(self):
model_options = {
"thermal": "x-lumped",
"external submodels": ["thermal"],
}
model = pybamm.lithium_ion.SPMe(model_options)
sim = pybamm.Simulation(model)
t_eval = np.linspace(0, 100, 3)
T_av = 0
for i in np.arange(1, len(t_eval) - 1):
dt = t_eval[i + 1] - t_eval[i]
external_variables = {"X-averaged cell temperature": T_av}
T_av += 1
sim.step(dt, external_variables=external_variables)
var = "Terminal voltage [V]"
t = sim.solution.t[-1]
y = sim.solution.y[:, -1]
u = external_variables
sim.built_model.variables[var].evaluate(t, y, u)
sim.solution[var](t)


if __name__ == "__main__":
import sys

print("Add -v for more debug output")

if "-v" in sys.argv:
debug = True
pybamm.settings.debug_mode = True
unittest.main()
10 changes: 5 additions & 5 deletions tests/unit/test_discretisations/test_discretisation.py
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,7 @@ def test_process_model_ode(self):
combined_submesh = mesh.combine_submeshes(*whole_cell)
disc.process_model(model)

y0 = model.concatenated_initial_conditions
y0 = model.concatenated_initial_conditions.evaluate()
np.testing.assert_array_equal(
y0, 3 * np.ones_like(combined_submesh[0].nodes[:, np.newaxis])
)
Expand Down Expand Up @@ -638,7 +638,7 @@ def test_process_model_ode(self):
model.variables = {"ST": S * T}

disc.process_model(model)
y0 = model.concatenated_initial_conditions
y0 = model.concatenated_initial_conditions.evaluate()
y0_expect = np.empty((0, 1))
for var_id, _ in sorted(disc.y_slices.items(), key=lambda kv: kv[1]):
if var_id == c.id:
Expand Down Expand Up @@ -716,7 +716,7 @@ def test_process_model_dae(self):
disc.process_model(model)
combined_submesh = mesh.combine_submeshes(*whole_cell)

y0 = model.concatenated_initial_conditions
y0 = model.concatenated_initial_conditions.evaluate()
np.testing.assert_array_equal(
y0,
np.concatenate(
Expand Down Expand Up @@ -815,7 +815,7 @@ def test_process_model_concatenation(self):
)

disc.process_model(model)
y0 = model.concatenated_initial_conditions
y0 = model.concatenated_initial_conditions.evaluate()
np.testing.assert_array_equal(
y0, 3 * np.ones_like(combined_submesh[0].nodes[:, np.newaxis])
)
Expand All @@ -842,7 +842,7 @@ def test_process_model_not_inplace(self):
submesh = mesh["negative electrode"]

discretised_model = disc.process_model(model, inplace=False)
y0 = discretised_model.concatenated_initial_conditions
y0 = discretised_model.concatenated_initial_conditions.evaluate()
np.testing.assert_array_equal(
y0, 3 * np.ones_like(submesh[0].nodes[:, np.newaxis])
)
Expand Down
1 change: 1 addition & 0 deletions tests/unit/test_solvers/test_base_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def test_ode_solver_fail_with_dae(self):
model = pybamm.BaseModel()
a = pybamm.Scalar(1)
model.algebraic = {a: a}
model.concatenated_initial_conditions = pybamm.Scalar(0)
solver = pybamm.ScipySolver()
with self.assertRaisesRegex(pybamm.SolverError, "Cannot use ODE solver"):
solver.set_up(model)
Expand Down
1 change: 1 addition & 0 deletions tests/unit/test_solvers/test_scikits_solvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,7 @@ def test_ode_solver_fail_with_dae(self):
model = pybamm.BaseModel()
a = pybamm.Scalar(1)
model.algebraic = {a: a}
model.concatenated_initial_conditions = a
solver = pybamm.ScikitsOdeSolver()
with self.assertRaisesRegex(pybamm.SolverError, "Cannot use ODE solver"):
solver.set_up(model)
Expand Down