Skip to content

Commit

Permalink
merge
Browse files Browse the repository at this point in the history
  • Loading branch information
valentinsulzer committed Oct 21, 2022
2 parents 14cdfff + f79ae87 commit 6266e87
Show file tree
Hide file tree
Showing 10 changed files with 251 additions and 203 deletions.
14 changes: 7 additions & 7 deletions pybamm/discretisations/discretisation.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def process_model(
model,
inplace=True,
check_model=True,
check_for_independent_variables=True,
remove_independent_variables_from_rhs=True,
):
"""Discretise a model.
Currently inplace, could be changed to return a new model.
Expand All @@ -127,7 +127,7 @@ def process_model(
option to False. When developing, testing or debugging it is recommended
to leave this option as True as it may help to identify any errors.
Default is True.
check_for_independent_variables : bool, optional
remove_independent_variables_from_rhs : bool, optional
If True, model checks to see whether any variables from the RHS are used
in any other equation. If a variable meets all of the following criteria
(not used anywhere in the model, len(rhs)>1), then the variable
Expand Down Expand Up @@ -171,8 +171,8 @@ def process_model(
# set variables (we require the full variable not just id)

# Search Equations for Independence
# if check_for_independent_variables:
# model = self.check_for_independent_variables(model)
# if remove_independent_variables_from_rhs:
# model = self.remove_independent_variables_from_rhs(model)
variables = list(model.rhs.keys()) + list(model.algebraic.keys())
# Find those RHS's that are constant
if self.spatial_methods == {} and any(var.domain != [] for var in variables):
Expand Down Expand Up @@ -1000,7 +1000,7 @@ def check_variables(self, model):
)
)

def search_for_independent_var(self, var, all_vars_in_eqns):
def is_variable_independent(self, var, all_vars_in_eqns):
pybamm.logger.verbose("Removing independent blocks.")
if not isinstance(var, pybamm.Variable):
return False
Expand All @@ -1014,7 +1014,7 @@ def search_for_independent_var(self, var, all_vars_in_eqns):
)
return this_var_is_independent

def check_for_independent_variables(self, model):
def remove_independent_variables_from_rhs(self, model):
rhs_vars_to_search_over = list(model.rhs.keys())
unpacker = pybamm.SymbolUnpacker(pybamm.Variable)
eqns_to_check = (
Expand All @@ -1033,7 +1033,7 @@ def check_for_independent_variables(self, model):
all_vars_in_eqns = [var.name for var in all_vars_in_eqns]

for var in rhs_vars_to_search_over:
this_var_is_independent = self.search_for_independent_var(
this_var_is_independent = self.is_variable_independent(
var, all_vars_in_eqns
)
if this_var_is_independent:
Expand Down
93 changes: 54 additions & 39 deletions pybamm/experiments/experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ def __init__(
if cccv_handling not in ["two-step", "ode"]:
raise ValueError("cccv_handling should be either 'two-step' or 'ode'")
self.cccv_handling = cccv_handling
# Save arguments for copying
self.args = (
operating_conditions,
period,
termination,
drive_cycles,
cccv_handling,
)

self.period = self.convert_time_to_seconds(period.split())
operating_conditions_cycles = []
Expand Down Expand Up @@ -116,6 +124,7 @@ def __init__(
cond for cycle in operating_conditions_cycles for cond in cycle
]
self.operating_conditions_cycles = operating_conditions_cycles
self.operating_conditions_strings = operating_conditions
self.operating_conditions = [
self.read_string(cond, drive_cycles) for cond in operating_conditions
]
Expand All @@ -124,7 +133,10 @@ def __init__(
self.termination = self.read_termination(termination)

def __str__(self):
return str([op["string"] for op in self.operating_conditions])
return str(self.operating_conditions_cycles)

def copy(self):
return Experiment(*self.args)

def __repr__(self):
return "pybamm.Experiment({!s})".format(self)
Expand All @@ -149,18 +161,23 @@ def read_string(self, cond, drive_cycles):
cond_CC, cond_CV = cond.split(" then ")
op_CC = self.read_string(cond_CC, drive_cycles)
op_CV = self.read_string(cond_CV, drive_cycles)
return {
outputs = {
"type": "CCCV",
"Current input [A]": op_CC["Current input [A]"],
"Voltage input [V]": op_CV["Voltage input [V]"],
"time": op_CV["time"],
"period": op_CV["period"],
"dc_data": None,
"events": op_CV["events"],
}
if "Current input [A]" in op_CC:
outputs["Current input [A]"] = op_CC["Current input [A]"]
else:
outputs["C-rate input [-]"] = op_CC["C-rate input [-]"]
return outputs

# Read period
if " period)" in cond:
cond, time_period = cond.split("(")
cond, time_period = cond.split(" (")
time, _ = time_period.split(" period)")
period = self.convert_time_to_seconds(time.split())
else:
Expand All @@ -176,36 +193,24 @@ def read_string(self, cond, drive_cycles):
"Type of drive cycle must be specified using '(A)', '(V)' or '(W)'."
f" For example: {examples}"
)
# Check for Events
elif "for" in cond:
# e.g. for 3 hours
idx = cond_list.index("for")
end_time = self.convert_time_to_seconds(cond_list[idx + 1 :])
ext_drive_cycle = self.extend_drive_cycle(
drive_cycles[cond_list[1]], end_time
)
# Drive cycle as numpy array
dc_name = cond_list[1] + "_ext_{}".format(end_time)
dc_data = ext_drive_cycle
# Find the type of drive cycle ("A", "V", or "W")
typ = cond_list[2][1]
electric = (dc_name, typ)
time = ext_drive_cycle[:, 0][-1]
period = np.min(np.diff(ext_drive_cycle[:, 0]))
events = None
else:
# e.g. Run US06
# Drive cycle as numpy array
dc_name = cond_list[1]
dc_data = drive_cycles[cond_list[1]]
# Find the type of drive cycle ("A", "V", or "W")
typ = cond_list[2][1]
electric = (dc_name, typ)
# Set time and period to 1 second for first step and
# then calculate the difference in consecutive time steps
time = drive_cycles[cond_list[1]][:, 0][-1]
period = np.min(np.diff(drive_cycles[cond_list[1]][:, 0]))
events = None
# Check for Events
if "for" in cond:
# e.g. for 3 hours
idx = cond_list.index("for")
end_time = self.convert_time_to_seconds(cond_list[idx + 1 :])
ext_drive_cycle = self.extend_drive_cycle(dc_data, end_time)
# Drive cycle as numpy array
dc_data = ext_drive_cycle
# Set time and period to 1 second for first step and
# then calculate the difference in consecutive time steps
time = dc_data[:, 0][-1]
period = np.min(np.diff(dc_data[:, 0]))
# Find the unit of drive cycle ("A", "V", or "W")
unit = cond_list[2][1]
electric = {"dc_data": dc_data, "unit": unit}
events = None
else:
dc_data = None
if "for" in cond and "or until" in cond:
Expand Down Expand Up @@ -236,6 +241,16 @@ def read_string(self, cond, drive_cycles):
f"'Run'. For example: {examples}"
)

unit = electric.pop("unit")
if unit == "C":
electric["type"] = "C-rate"
elif unit == "A":
electric["type"] = "current"
elif unit == "V":
electric["type"] = "voltage"
elif unit == "W":
electric["type"] = "power"

return {
**electric,
"time": time,
Expand Down Expand Up @@ -323,13 +338,13 @@ def convert_electric(self, electric):
value = float(value_unit[:-1])
# Read value and units
if unit == "C":
return {"C-rate input [-]": sign * float(value), "type": "C-rate"}
return {"C-rate input [-]": sign * float(value), "unit": unit}
elif unit == "A":
return {"Current input [A]": sign * float(value), "type": "current"}
return {"Current input [A]": sign * float(value), "unit": unit}
elif unit == "V":
return {"Voltage input [V]": float(value), "type": "voltage"}
return {"Voltage input [V]": float(value), "unit": unit}
elif unit == "W":
return {"Power input [W]": sign * float(value), "type": "power"}
return {"Power input [W]": sign * float(value), "unit": unit}
else:
raise ValueError(
"""units must be 'C', 'A', 'mA', 'V', 'W' or 'mW', not '{}'.
Expand Down Expand Up @@ -410,9 +425,9 @@ def is_cccv(self, step, next_step):
and "Hold at " in next_step
and "V until" in next_step
):
_, events = self.read_string(step, None)
next_op, _ = self.read_string(next_step, None)
op = self.read_string(step, None)
next_op = self.read_string(next_step, None)
# Check that the event conditions are the same as the hold conditions
if events == next_op["electric"]:
if op["events"] == {k: v for k, v in next_op.items() if k in op["events"]}:
return True
return False
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,15 @@ def get_fundamental_variables(self):
if self.control in ["algebraic", "differential without max"]:
i_cell = i_var
elif self.control == "differential with max":
current_function = (
i_input = (
pybamm.FunctionParameter(
"CCCV current function [A]", {"Time[s]": pybamm.t * param.timescale}
"CCCV current function [A]",
{"Time [s]": pybamm.t * param.timescale},
)
/ self.I_typ
* pybamm.sign(self.I_typ)
/ param.I_typ
* pybamm.sign(param.I_typ)
)
i_cell = pybamm.maximum(i_var, current_function)
i_cell = pybamm.maximum(i_var, i_input)

# Update derived variables
I = i_cell * abs(param.I_typ)
Expand Down
47 changes: 24 additions & 23 deletions pybamm/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,6 @@ def __init__(
current = self._parameter_values.get("Current function [A]")
if isinstance(current, pybamm.Interpolant):
self.operating_mode = "drive cycle"
elif isinstance(current, tuple):
raise NotImplementedError(
"Drive cycle from data has been deprecated. "
+ "Define an Interpolant instead."
)
else:
self.operating_mode = "without experiment"
if C_rate:
Expand All @@ -99,7 +94,7 @@ def __init__(
else:
self.operating_mode = "with experiment"
# Save the experiment
self.experiment = experiment
self.experiment = experiment.copy()

self._unprocessed_model = model
self.model = model
Expand Down Expand Up @@ -169,15 +164,22 @@ def set_up_and_parameterise_experiment(self):
Crate = op["Current input [A]"] / capacity

# Update events
events = op["events"]
events = op.pop("events")
if events is not None:
event_type = events.pop("type")
if event_type == "C-rate":
# Scale C-rate with capacity to obtain current
events["Current cut-off [A]"] = (
events["C-rate cut-off [C]"] * capacity
)
op.update(events)
# Update the dictionary of operating conditions, replacing
# "xxx input [unit]" with "xxx cut-off [unit]"
op.update(
{
key.replace("input", "cut-off"): value
for key, value in events.items()
}
)

# Add time to the experiment times
dt = op["time"]
Expand Down Expand Up @@ -208,12 +210,12 @@ def set_up_and_parameterise_model_for_experiment(self):
self.op_type_to_model = {}
self.op_string_to_model = {}
for op in self.experiment.operating_conditions:
# Create model for this operating condition if it has not already been seen
# before
# Create model for this operating condition type (current/voltage/power)
# if it has not already been seen before
if op["type"] not in self.op_type_to_model:
# Make a new copy of the model (we will update events later))
new_model = model.new_copy()
if op["type"] != "current":
if op["type"] == "current":
new_model, submodel = model, None
else:
# Voltage or power control
# Create a new model where the current density is now a variable
# To do so, we replace all instances of the current density in the
Expand All @@ -228,8 +230,9 @@ def set_up_and_parameterise_model_for_experiment(self):
elif op["type"] == "CCCV":
submodel_class = pybamm.external_circuit.CCCVFunctionControl

new_model = model.new_copy()
# Build the new submodel and update the model with it
submodel = submodel_class(new_model.param, new_model.options)

variables = new_model.variables
submodel.variables = submodel.get_fundamental_variables()
variables.update(submodel.variables)
Expand All @@ -241,17 +244,16 @@ def set_up_and_parameterise_model_for_experiment(self):
new_model.rhs.update(submodel.rhs)
new_model.algebraic.update(submodel.algebraic)
new_model.initial_conditions.update(submodel.initial_conditions)
else:
submodel = None

self.update_new_model_events(new_model, op)

self.op_type_to_model[op["type"]] = (new_model, submodel)

else:
new_model, submodel = self.op_type_to_model[op["type"]]

if op["string"] not in self.op_string_to_model:
model, submodel = self.op_type_to_model[op["type"]]
# Create a new model for this operating condition, since we will update
# the events differently (based on parameter values and inputs) for
# different models of the same type (current/voltage/power)
new_model = model.new_copy()
self.update_new_model_events(new_model, op)
# Update parameter values
new_parameter_values = self.parameter_values.copy()
experiment_parameter_values = self.get_experiment_parameter_values(op)
Expand Down Expand Up @@ -713,7 +715,6 @@ def solve(
for step_num in range(1, cycle_length + 1):
# Use 1-indexing for printing cycle number as it is more
# human-intuitive
# op = self._experiment_inputs[idx]
op_conds = self.experiment.operating_conditions[idx]
dt = op_conds["time"]
op_conds_str = op_conds["string"]
Expand All @@ -726,7 +727,7 @@ def solve(
start_time = current_solution.t[-1]
kwargs["inputs"] = {
**user_inputs,
# **op,
**op_conds,
"start time": start_time,
}
# Make sure we take at least 2 timesteps
Expand Down
Loading

0 comments on commit 6266e87

Please sign in to comment.