@@ -175,6 +175,12 @@ def set_up_experiment(self, model, experiment):
175
175
new_model .variables
176
176
)
177
177
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
+ ]
178
184
# add current and voltage events to the model
179
185
# current events both negative and positive to catch specification
180
186
new_model .events .extend (
@@ -330,7 +336,15 @@ def build(self, check_model=True):
330
336
self ._model_with_set_params , inplace = False , check_model = check_model
331
337
)
332
338
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
+ ):
334
348
"""
335
349
A method to solve the model. This method will automatically build
336
350
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):
353
367
If None and the parameter "Current function [A]" is read from data
354
368
(i.e. drive cycle simulation) the model will be solved at the times
355
369
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
358
372
check_model : bool, optional
359
373
If True, model checks are performed after discretisation (see
360
374
: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.
361
382
**kwargs
362
383
Additional key-word arguments passed to `solver.solve`.
363
384
See :meth:`pybamm.BaseSolver.solve`.
@@ -368,7 +389,15 @@ def solve(self, t_eval=None, solver=None, check_model=True, **kwargs):
368
389
solver = self .solver
369
390
370
391
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
+ )
372
401
if self .operating_mode == "without experiment" :
373
402
if t_eval is None :
374
403
raise pybamm .SolverError (
@@ -436,99 +465,82 @@ def solve(self, t_eval=None, solver=None, check_model=True, **kwargs):
436
465
)
437
466
# Re-initialize solution, e.g. for solving multiple times with different
438
467
# inputs without having to build the simulation again
439
- self ._solution = None
440
- previous_num_subsolutions = 0
468
+ self ._solution = starting_solution
441
469
# Step through all experimental conditions
442
470
inputs = kwargs .get ("inputs" , {})
443
471
pybamm .logger .info ("Start running experiment" )
444
472
timer = pybamm .Timer ()
445
473
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
447
482
448
483
idx = 0
449
484
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
455
491
)
456
492
steps = []
457
493
cycle_solution = None
458
- for step_num in range (cycle_length ):
494
+
495
+ for step_num in range (1 , cycle_length + 1 ):
459
496
exp_inputs = self ._experiment_inputs [idx ]
460
497
dt = self ._experiment_times [idx ]
461
498
# Use 1-indexing for printing cycle number as it is more
462
499
# 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 } : "
466
503
f"{ self .experiment .operating_conditions_strings [idx ]} "
467
504
)
468
505
inputs .update (exp_inputs )
469
506
kwargs ["inputs" ] = inputs
470
507
# Make sure we take at least 2 timesteps
471
508
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 ,
491
516
)
492
- step_solution .solve_time = 0
493
- step_solution .integration_time = 0
494
517
steps .append (step_solution )
518
+ current_solution = step_solution
495
519
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
502
521
503
522
# Only allow events specified by experiment
504
523
if not (
505
- self ._solution .termination == "final time"
524
+ self ._solution is None
525
+ or self ._solution .termination == "final time"
506
526
or "[experiment]" in self ._solution .termination
507
527
):
508
- feasible = False
528
+ pybamm .logger .warning (
529
+ "\n \n \t Experiment 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
+ )
509
538
break
510
539
511
540
# Increment index for next iteration
512
541
idx += 1
513
542
514
- # Break if the experiment is infeasible
515
- if feasible is False :
516
- pybamm .logger .warning (
517
- "\n \n \t Experiment 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
-
530
543
# At the final step of the inner loop we save the cycle
531
- cycle_solution .steps = steps
532
544
all_cycle_solutions .append (cycle_solution )
533
545
534
546
self .solution .cycles = all_cycle_solutions
@@ -539,7 +551,9 @@ def solve(self, t_eval=None, solver=None, check_model=True, **kwargs):
539
551
540
552
return self .solution
541
553
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
+ ):
543
557
"""
544
558
A method to step the model forward one timestep. This method will
545
559
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):
555
569
the step dt. Default is 2 (returns the solution at t0 and t0 + dt).
556
570
save : bool
557
571
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
558
575
**kwargs
559
576
Additional key-word arguments passed to `solver.solve`.
560
577
See :meth:`pybamm.BaseSolver.step`.
@@ -564,8 +581,11 @@ def step(self, dt, solver=None, npts=2, save=True, **kwargs):
564
581
if solver is None :
565
582
solver = self .solver
566
583
584
+ if starting_solution is None :
585
+ starting_solution = self ._solution
586
+
567
587
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
569
589
)
570
590
571
591
return self .solution
0 commit comments