Skip to content

Commit 349ce68

Browse files
#1276 reformat solution with experiment
1 parent e1c7273 commit 349ce68

File tree

4 files changed

+99
-77
lines changed

4 files changed

+99
-77
lines changed

pybamm/simulation.py

+84-64
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,12 @@ def set_up_experiment(self, model, experiment):
175175
new_model.variables
176176
)
177177

178+
# Remove upper and lower voltage cut-offs that are *not* part of the experiment
179+
new_model.events = [
180+
event
181+
for event in model.events
182+
if event.name not in ["Minimum voltage", "Maximum voltage"]
183+
]
178184
# add current and voltage events to the model
179185
# current events both negative and positive to catch specification
180186
new_model.events.extend(
@@ -330,7 +336,15 @@ def build(self, check_model=True):
330336
self._model_with_set_params, inplace=False, check_model=check_model
331337
)
332338

333-
def solve(self, t_eval=None, solver=None, check_model=True, **kwargs):
339+
def solve(
340+
self,
341+
t_eval=None,
342+
solver=None,
343+
check_model=True,
344+
save_at_cycles=None,
345+
starting_solution=None,
346+
**kwargs,
347+
):
334348
"""
335349
A method to solve the model. This method will automatically build
336350
and set the model parameters if not already done so.
@@ -353,11 +367,18 @@ def solve(self, t_eval=None, solver=None, check_model=True, **kwargs):
353367
If None and the parameter "Current function [A]" is read from data
354368
(i.e. drive cycle simulation) the model will be solved at the times
355369
provided in the data.
356-
solver : :class:`pybamm.BaseSolver`
357-
The solver to use to solve the model.
370+
solver : :class:`pybamm.BaseSolver`, optional
371+
The solver to use to solve the model. If None, Simulation.solver is used
358372
check_model : bool, optional
359373
If True, model checks are performed after discretisation (see
360374
:meth:`pybamm.Discretisation.process_model`). Default is True.
375+
save_at_cycles : int or list of ints, optional
376+
Which cycles to save the full sub-solutions for. If None, all cycles are
377+
saved. If int, every multiple of save_at_cycles is saved. If list, every
378+
cycle in the list is saved.
379+
starting_solution : :class:`pybamm.Solution`
380+
The solution to start stepping from. If None (default), then self._solution
381+
is used. Must be None if not using an experiment.
361382
**kwargs
362383
Additional key-word arguments passed to `solver.solve`.
363384
See :meth:`pybamm.BaseSolver.solve`.
@@ -368,7 +389,15 @@ def solve(self, t_eval=None, solver=None, check_model=True, **kwargs):
368389
solver = self.solver
369390

370391
if self.operating_mode in ["without experiment", "drive cycle"]:
371-
392+
if save_at_cycles is not None:
393+
raise ValueError(
394+
"'save_at_cycles' option can only be used if simulating an "
395+
"Experiment "
396+
)
397+
if starting_solution is not None:
398+
raise ValueError(
399+
"starting_solution can only be provided if simulating an Experiment"
400+
)
372401
if self.operating_mode == "without experiment":
373402
if t_eval is None:
374403
raise pybamm.SolverError(
@@ -436,99 +465,82 @@ def solve(self, t_eval=None, solver=None, check_model=True, **kwargs):
436465
)
437466
# Re-initialize solution, e.g. for solving multiple times with different
438467
# inputs without having to build the simulation again
439-
self._solution = None
440-
previous_num_subsolutions = 0
468+
self._solution = starting_solution
441469
# Step through all experimental conditions
442470
inputs = kwargs.get("inputs", {})
443471
pybamm.logger.info("Start running experiment")
444472
timer = pybamm.Timer()
445473

446-
all_cycle_solutions = []
474+
if starting_solution is None:
475+
starting_solution_cycles = []
476+
else:
477+
starting_solution_cycles = starting_solution.cycles
478+
479+
cycle_offset = len(starting_solution_cycles)
480+
all_cycle_solutions = starting_solution_cycles
481+
current_solution = starting_solution
447482

448483
idx = 0
449484
num_cycles = len(self.experiment.cycle_lengths)
450-
feasible = True # simulation will stop if experiment is infeasible
451-
for cycle_num, cycle_length in enumerate(self.experiment.cycle_lengths):
452-
pybamm.logger.info(
453-
f"Cycle {cycle_num+1}/{num_cycles} ({timer.time()} elapsed) "
454-
+ "-" * 20
485+
for cycle_num, cycle_length in enumerate(
486+
self.experiment.cycle_lengths, start=1
487+
):
488+
pybamm.logger.notice(
489+
f"Cycle {cycle_num+cycle_offset}/{num_cycles+cycle_offset} "
490+
f"({timer.time()} elapsed) " + "-" * 20
455491
)
456492
steps = []
457493
cycle_solution = None
458-
for step_num in range(cycle_length):
494+
495+
for step_num in range(1, cycle_length + 1):
459496
exp_inputs = self._experiment_inputs[idx]
460497
dt = self._experiment_times[idx]
461498
# Use 1-indexing for printing cycle number as it is more
462499
# human-intuitive
463-
pybamm.logger.info(
464-
f"Cycle {cycle_num+1}/{num_cycles}, "
465-
f"step {step_num+1}/{cycle_length}: "
500+
pybamm.logger.notice(
501+
f"Cycle {cycle_num+cycle_offset}/{num_cycles+cycle_offset}, "
502+
f"step {step_num}/{cycle_length}: "
466503
f"{self.experiment.operating_conditions_strings[idx]}"
467504
)
468505
inputs.update(exp_inputs)
469506
kwargs["inputs"] = inputs
470507
# Make sure we take at least 2 timesteps
471508
npts = max(int(round(dt / exp_inputs["period"])) + 1, 2)
472-
self.step(dt, solver=solver, npts=npts, **kwargs)
473-
474-
# Extract the new parts of the solution
475-
# to construct the entire "step"
476-
sol = self.solution
477-
new_num_subsolutions = len(sol.sub_solutions)
478-
diff_num_subsolutions = (
479-
new_num_subsolutions - previous_num_subsolutions
480-
)
481-
previous_num_subsolutions = new_num_subsolutions
482-
483-
step_solution = pybamm.Solution(
484-
sol.all_ts[-diff_num_subsolutions:],
485-
sol.all_ys[-diff_num_subsolutions:],
486-
sol.model,
487-
sol.all_inputs[-diff_num_subsolutions:],
488-
sol.t_event,
489-
sol.y_event,
490-
sol.termination,
509+
step_solution = solver.step(
510+
current_solution,
511+
self.built_model,
512+
dt,
513+
npts=npts,
514+
save=False,
515+
**kwargs,
491516
)
492-
step_solution.solve_time = 0
493-
step_solution.integration_time = 0
494517
steps.append(step_solution)
518+
current_solution = step_solution
495519

496-
# Construct cycle solutions (a list of solutions corresponding to
497-
# cycles) from sub_solutions
498-
if step_num == 0:
499-
cycle_solution = step_solution
500-
else:
501-
cycle_solution = cycle_solution + step_solution
520+
self._solution = self.solution + current_solution
502521

503522
# Only allow events specified by experiment
504523
if not (
505-
self._solution.termination == "final time"
524+
self._solution is None
525+
or self._solution.termination == "final time"
506526
or "[experiment]" in self._solution.termination
507527
):
508-
feasible = False
528+
pybamm.logger.warning(
529+
"\n\n\tExperiment is infeasible: '{}' ".format(
530+
self._solution.termination
531+
)
532+
+ "was triggered during '{}'. ".format(
533+
self.experiment.operating_conditions_strings[idx]
534+
)
535+
+ "Try reducing current, shortening the time interval, "
536+
"or reducing the period.\n\n"
537+
)
509538
break
510539

511540
# Increment index for next iteration
512541
idx += 1
513542

514-
# Break if the experiment is infeasible
515-
if feasible is False:
516-
pybamm.logger.warning(
517-
"\n\n\tExperiment is infeasible: '{}' ".format(
518-
self._solution.termination
519-
)
520-
+ "was triggered during '{}'. ".format(
521-
self.experiment.operating_conditions_strings[idx]
522-
)
523-
+ "The returned solution only contains the first "
524-
"{} cycles. ".format(cycle_num)
525-
+ "Try reducing the current, shortening the time interval, "
526-
"or reducing the period.\n\n"
527-
)
528-
break
529-
530543
# At the final step of the inner loop we save the cycle
531-
cycle_solution.steps = steps
532544
all_cycle_solutions.append(cycle_solution)
533545

534546
self.solution.cycles = all_cycle_solutions
@@ -539,7 +551,9 @@ def solve(self, t_eval=None, solver=None, check_model=True, **kwargs):
539551

540552
return self.solution
541553

542-
def step(self, dt, solver=None, npts=2, save=True, **kwargs):
554+
def step(
555+
self, dt, solver=None, npts=2, save=True, starting_solution=None, **kwargs
556+
):
543557
"""
544558
A method to step the model forward one timestep. This method will
545559
automatically build and set the model parameters if not already done so.
@@ -555,6 +569,9 @@ def step(self, dt, solver=None, npts=2, save=True, **kwargs):
555569
the step dt. Default is 2 (returns the solution at t0 and t0 + dt).
556570
save : bool
557571
Turn on to store the solution of all previous timesteps
572+
starting_solution : :class:`pybamm.Solution`
573+
The solution to start stepping from. If None (default), then self._solution
574+
is used
558575
**kwargs
559576
Additional key-word arguments passed to `solver.solve`.
560577
See :meth:`pybamm.BaseSolver.step`.
@@ -564,8 +581,11 @@ def step(self, dt, solver=None, npts=2, save=True, **kwargs):
564581
if solver is None:
565582
solver = self.solver
566583

584+
if starting_solution is None:
585+
starting_solution = self._solution
586+
567587
self._solution = solver.step(
568-
self._solution, self.built_model, dt, npts=npts, save=save, **kwargs
588+
starting_solution, self.built_model, dt, npts=npts, save=save, **kwargs
569589
)
570590

571591
return self.solution

pybamm/solvers/base_solver.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -995,7 +995,7 @@ def step(
995995
)
996996

997997
# Return solution
998-
if save is False or old_solution is None:
998+
if save is False:
999999
return solution
10001000
else:
10011001
return old_solution + solution

pybamm/solvers/casadi_solver.py

+4-10
Original file line numberDiff line numberDiff line change
@@ -336,11 +336,8 @@ def event_fun(t):
336336
# assign temporary solve time
337337
current_step_sol.solve_time = np.nan
338338

339-
if solution is None:
340-
solution = current_step_sol
341-
else:
342-
# append solution from the current step to solution
343-
solution = solution + current_step_sol
339+
# append solution from the current step to solution
340+
solution = solution + current_step_sol
344341
solution.termination = "event"
345342
solution.t_event = np.array([t_event])
346343
solution.y_event = y_event[:, np.newaxis]
@@ -349,11 +346,8 @@ def event_fun(t):
349346
else:
350347
# assign temporary solve time
351348
current_step_sol.solve_time = np.nan
352-
if solution is None:
353-
solution = current_step_sol
354-
else:
355-
# append solution from the current step to solution
356-
solution = solution + current_step_sol
349+
# append solution from the current step to solution
350+
solution = solution + current_step_sol
357351
# update time
358352
t = t_window[-1]
359353
# update y0

pybamm/solvers/solution.py

+10-2
Original file line numberDiff line numberDiff line change
@@ -385,8 +385,7 @@ def save_data(self, filename, variables=None, to_format="pickle", short_names=No
385385

386386
@property
387387
def sub_solutions(self):
388-
"""List of sub solutions that have been concatenated to form the full solution
389-
"""
388+
"""List of sub solutions that have been concatenated to form the full solution"""
390389
return self._sub_solutions
391390

392391
def __add__(self, other):
@@ -435,6 +434,15 @@ def __add__(self, other):
435434

436435
return new_sol
437436

437+
def __radd__(self, other):
438+
"""
439+
Function to deal with the case `None + Solution` (returns `Solution`)
440+
"""
441+
if other is None:
442+
return self.copy()
443+
else:
444+
return other + self
445+
438446
def copy(self):
439447
new_sol = Solution(
440448
self.all_ts,

0 commit comments

Comments
 (0)