Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions .trunk/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
*out
*logs
*actions
*notifications
*tools
plugins
user_trunk.yaml
user.yaml
tmp
2 changes: 2 additions & 0 deletions .trunk/configs/.isort.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[settings]
profile=black
10 changes: 10 additions & 0 deletions .trunk/configs/.markdownlint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Autoformatter friendly markdownlint config (all formatting rules disabled)
default: true
blank_lines: false
bullet: false
html: false
indentation: false
line_length: false
spaces: false
url: false
whitespace: false
10 changes: 10 additions & 0 deletions .trunk/configs/.yamllint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
rules:
quoted-strings:
required: only-when-needed
extra-allowed: ["{|}"]
empty-values:
forbid-in-block-mappings: true
forbid-in-flow-mappings: true
key-duplicates: {}
octal-values:
forbid-implicit-octal: true
5 changes: 5 additions & 0 deletions .trunk/configs/ruff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Generic, formatter-friendly config.
select = ["B", "D3", "E", "F"]

# Never enforce `E501` (line length violations). This should be handled by formatters.
ignore = ["E501"]
41 changes: 41 additions & 0 deletions .trunk/trunk.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# This file controls the behavior of Trunk: https://docs.trunk.io/cli
# To learn more about the format of this file, see https://docs.trunk.io/reference/trunk-yaml
version: 0.1
cli:
version: 1.19.0
# Trunk provides extensibility via plugins. (https://docs.trunk.io/plugins)
plugins:
sources:
- id: trunk
ref: v1.4.2
uri: https://github.com/trunk-io/plugins
# Many linters and tools depend on runtimes - configure them here. (https://docs.trunk.io/runtimes)
runtimes:
enabled:
- node@18.12.1
- python@3.10.8
# This is the section where you manage your linters. (https://docs.trunk.io/check/configuration)
lint:
enabled:
- actionlint@1.6.26
- bandit@1.7.7
- black@24.1.1
- checkov@3.2.2
- flake8@7.0.0
- git-diff-check
- isort@5.13.2
- markdownlint@0.39.0
- osv-scanner@1.6.2
- oxipng@9.0.0
- prettier@3.2.4
- ruff@0.1.15
- trivy@0.48.3
- trufflehog@3.66.2
- yamllint@1.33.0
actions:
disabled:
- trunk-announce
- trunk-check-pre-push
- trunk-fmt-pre-commit
enabled:
- trunk-upgrade-available
15 changes: 15 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python: Module",
"type": "python",
"request": "launch",
"module": "ecm",
"justMyCode": false,
}
]
}
110 changes: 74 additions & 36 deletions docs/examples/01 Getting started.ipynb

Large diffs are not rendered by default.

174 changes: 77 additions & 97 deletions docs/examples/02 Network from an image.ipynb

Large diffs are not rendered by default.

282 changes: 209 additions & 73 deletions docs/examples/03 Running a simulation.ipynb

Large diffs are not rendered by default.

114 changes: 42 additions & 72 deletions ecm/__funcs__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def adjust_parameters(parameter_values, I_typical):
"Typical current [A]": I_typical,
"Current function [A]": current_function,
"Electrode height [m]": "[input]",
}
},check_already_exists=False
)
parameter_values.update({"Current": "[input]"}, check_already_exists=False)
return parameter_values
Expand All @@ -69,7 +69,7 @@ def output_variables():
"X-averaged battery concentration overpotential [V]",
"X-averaged battery electrolyte ohmic losses [V]",
"X-averaged battery solid phase ohmic losses [V]",
"Change in measured open circuit voltage [V]",
"Bulk open-circuit voltage [V]",
]


Expand Down Expand Up @@ -157,24 +157,24 @@ def run_ecm(net, alg, V_terminal, plot=False):
def setup_geometry(net, dtheta, spacing, length_3d):
# Create Geometry based on circular arc segment
drad = np.deg2rad(dtheta)
geo = op.geometry.GenericGeometry(network=net, pores=net.Ps, throats=net.Ts)
# geo = op.geometry.GenericGeometry(network=net, pores=net.Ps, throats=net.Ts)
if "throat.radial_position" not in net.props():
geo["throat.radial_position"] = net.interpolate_data("pore.radial_position")
geo["pore.volume"] = net["pore.radial_position"] * drad * spacing * length_3d
net["throat.radial_position"] = net.interpolate_data("throat.radial_position")
net["pore.volume"] = net["pore.radial_position"] * drad * spacing * length_3d
cn = net["throat.conns"]
C1 = net["pore.coords"][cn[:, 0]]
C2 = net["pore.coords"][cn[:, 1]]
D = np.sqrt(((C1 - C2) ** 2).sum(axis=1))
geo["throat.length"] = D
net["throat.length"] = D
# Work out if throat connects pores in same radial position
rPs = geo["pore.arc_index"][net["throat.conns"]]
rPs = net["pore.arc_index"][net["throat.conns"]]
sameR = rPs[:, 0] == rPs[:, 1]
geo["throat.area"] = spacing * length_3d
geo["throat.electrode_height"] = geo["throat.radial_position"] * drad
geo["throat.area"][sameR] = geo["throat.electrode_height"][sameR] * length_3d
geo["throat.volume"] = 0.0
geo["throat.volume"][sameR] = geo["throat.area"][sameR] * spacing
return geo
net["throat.area"] = spacing * length_3d
net["throat.electrode_height"] = net["throat.radial_position"] * drad
net["throat.area"][sameR] = net["throat.electrode_height"][sameR] * length_3d
net["throat.volume"] = 0.0
net["throat.volume"][sameR] = net["throat.area"][sameR] * spacing
return net


def setup_thermal(project, parameter_values):
Expand All @@ -184,23 +184,23 @@ def setup_thermal(project, parameter_values):
rho = lumpy_therm["lump_rho"]
total_htc = parameter_values["Total heat transfer coefficient [W.m-2.K-1]"]
net = project.network
geo = project.geometries()["geo_01"]
phase = project.phases()["phase_01"]
phys = project.physics()["phys_01"]
# geo = project.geometries()["geo_01"]
phase = project["phase_01"]
# phys = project.physics()["phys_01"]
hc = total_htc / (cp * rho)
# Set up Phase and Physics
phase["pore.temperature"] = T0
alpha_spiral = lumpy_therm["alpha_spiral"]
alpha_radial = lumpy_therm["alpha_radial"]
phys["throat.conductance"] = 1.0 * geo["throat.area"] / geo["throat.length"]
phase["throat.thermal_conductance"] = 1.0 * net["throat.area"] / net["throat.length"]
# Apply anisotropic heat conduction
Ts = net.throats("spm_resistor")
phys["throat.conductance"][Ts] *= alpha_radial
phase["throat.thermal_conductance"][Ts] *= alpha_radial
Ts = net.throats("spm_resistor", mode="not")
phys["throat.conductance"][Ts] *= alpha_spiral
phase["throat.thermal_conductance"][Ts] *= alpha_spiral
# Free stream convective flux
Ts = net.throats("free_stream")
phys["throat.conductance"][Ts] = geo["throat.area"][Ts] * hc
phase["throat.thermal_conductance"][Ts] = phase["throat.area"][Ts] * hc

# print("Mean throat conductance", np.mean(phys["throat.conductance"]))
# print("Mean throat conductance Boundary", np.mean(phys["throat.conductance"][Ts]))
Expand All @@ -210,11 +210,11 @@ def apply_heat_source(project, Q):
# The SPMs are defined at the throat but the pores represent the
# Actual electrode volume so need to interpolate for heat sources
net = project.network
phys = project.physics()["phys_01"]
phase = project.phases[0]
spm_Ts = net.throats("spm_resistor")
phys["throat.heat_source"] = 0.0
phys["throat.heat_source"][spm_Ts] = Q
phys.add_model(
phase["throat.heat_source"] = 0.0
phase["throat.heat_source"][spm_Ts] = Q
phase.add_model(
propname="pore.heat_source",
model=op.models.misc.from_neighbor_throats,
prop="throat.heat_source",
Expand All @@ -225,80 +225,50 @@ def apply_heat_source(project, Q):
def apply_heat_source_lp(project, Q):
# The SPMs are defined at the throat but the pores represent the
# Actual electrode volume so need to interpolate for heat sources
phys = project.physics()["phys_01"]
phys["throat.heat_source"] = Q
phys.add_model(
# net = project.network
phase = project.phases[0]
phase["throat.heat_source"] = Q
phase.add_model(
propname="pore.heat_source",
model=op.models.misc.from_neighbor_throats,
prop="throat.heat_source",
mode="mean",
)

default_int = rk45 = op.integrators.ScipyRadau(verbose=True)

def run_step_transient(project, time_step, BC_value, cp, rho, third=False):

def run_step_transient(project, time_step, BC_value, cp, rho, integrator = default_int, third=False):
# To Do - test whether this needs to be transient
net = project.network
phase = project.phases()["phase_01"]
phys = project.physics()["phys_01"]
phys["pore.A1"] = 0.0
Q_spm = phys["pore.heat_source"] * net["pore.volume"]
# Q_cc = net["pore.cc_power_loss"]
# print(
# "Q_spm",
# np.around(np.sum(Q_spm), 2),
# "\n",
# "Q_cc",
# np.around(np.sum(Q_cc), 2),
# "\n",
# "ratio Q_cc/Q_spm",
# np.around(np.sum(Q_cc) / np.sum(Q_spm), 2),
# )
# phys["pore.A2"] = (Q_spm + Q_cc) / (cp * rho)
phys["pore.A2"] = (Q_spm) / (cp * rho)
phase = project.phases[0]

# Heat Source
T0 = phase["pore.temperature"]
t_step = float(time_step / 10)
phys.add_model(
"pore.source",
model=linear,
X="pore.temperature",
A1="pore.A1",
A2="pore.A2",
)
integrator = op.integrators.ScipyLSODA(verbose=True,min_step=t_step/100)

# Run Transient Heat Transport Algorithm
alg = op.algorithms.TransientReactiveTransport(network=net)
alg.setup(
phase=phase,
conductance="throat.conductance",
quantity="pore.temperature",
t_initial=0.0,
t_final=time_step,
t_step=t_step,
t_output=t_step,
t_tolerance=1e-9,
t_precision=12,
rxn_tolerance=1e-9,
t_scheme="implicit",
)
alg.set_IC(values=T0)
alg = op.algorithms.TransientFourierConduction(network=net, phase=phase)

bulk_Ps = net.pores("free_stream", mode="not")
alg.set_source("pore.source", bulk_Ps)
alg.set_source(bulk_Ps, "pore.temperature")
if third:
# To do - 12 only works if detheta is 10
free_pores = net.pores("free_stream")
Ps = free_pores[net["pore.arc_index"][free_pores] < 12]
else:
Ps = net.pores("free_stream")
alg.set_value_BC(Ps, values=BC_value)
alg.run()
alg.run(x0=T0, tspan=(0.0, time_step),integrator=default_int)
# print(
# "Max Temp",
# np.around(alg["pore.temperature"].max(), 3),
# "Min Temp",
# np.around(alg["pore.temperature"].min(), 3),
# )
phase["pore.temperature"] = alg["pore.temperature"]
project.purge_object(alg)
# project.purge_object(alg)


def setup_pool(max_workers, pool_type="Process"):
Expand Down Expand Up @@ -419,8 +389,8 @@ def interpolate_spm_number(project, x_len=2000, y_len=2000):
myInterpolator = NearestNDInterpolator(points, all_data)
f = 1.05
grid_x, grid_y = np.mgrid[
x.min() * f : x.max() * f : np.complex(x_len, 0),
y.min() * f : y.max() * f : np.complex(y_len, 0),
x.min() * f : x.max() * f : complex(x_len, 0),
y.min() * f : y.max() * f : complex(y_len, 0),
]
arr = myInterpolator(grid_x, grid_y, 0)
# arr[arr == -1] = np.nan
Expand Down
14 changes: 8 additions & 6 deletions ecm/__liionsolve__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,14 @@ def do_heating():
pass


def run_simulation_lp(parameter_values, experiment, initial_soc, project):
def run_simulation_lp(parameter_values, experiment, initial_soc, project, integrator=None):
###########################################################################
# Simulation information #
###########################################################################
integrator = integrator or op.integrators.ScipyRadau()
st = ticker.time()
max_workers = int(os.cpu_count() / 2)
max_workers = 1
# hours = config.getfloat("RUN", "hours")
# try:
# dt = config.getfloat("RUN", "dt")
Expand All @@ -50,7 +52,7 @@ def run_simulation_lp(parameter_values, experiment, initial_soc, project):
# dt = 30
# Nsteps = np.int(hours * 60 * 2) + 1 # number of time steps
net = project.network
phase = project.phases()["phase_01"]
phase = project["phase_01"]
# The jellyroll layers are double sided around the cc except for the inner
# and outer layers the number of spm models is the number of throat
# connections between cc layers
Expand Down Expand Up @@ -173,9 +175,9 @@ def run_simulation_lp(parameter_values, experiment, initial_soc, project):
Q[res_Ts] += Q_tot
ecm.apply_heat_source_lp(project, Q)
# Calculate Global Temperature
ecm.run_step_transient(project, dim_time_step, T0, cp, rho, thermal_third)
ecm.run_step_transient(project, dim_time_step, T0, cp, rho,integrator=integrator)
# Interpolate the node temperatures for the SPMs
spm_temperature = phase.interpolate_data("pore.temperature")[res_Ts]
spm_temperature = phase.interpolate_data("throat.temperature")[res_Ts] #! maybe should be the throat temp
# T_non_dim_spm = fT_non_dim(parameter_values, spm_temperature)
###################################################################
step += 1
Expand Down Expand Up @@ -233,9 +235,9 @@ def run_simulation_lp(parameter_values, experiment, initial_soc, project):
Q[res_Ts] += Q_tot
ecm.apply_heat_source_lp(project, Q)
# Calculate Global Temperature
ecm.run_step_transient(project, dim_time_step, T0, cp, rho, thermal_third)
ecm.run_step_transient(project, dim_time_step, T0, cp, rho,integrator=integrator)
# Interpolate the node temperatures for the SPMs
spm_temperature = phase.interpolate_data("pore.temperature")[res_Ts]
spm_temperature = phase.interpolate_data("throat.temperature")[res_Ts]
###################################################################
step += 1
pbar.update(1)
Expand Down
8 changes: 4 additions & 4 deletions ecm/__segment_jellyroll__.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def average_images(path=None):
boxes = np.asarray(boxes)
x_span = boxes[:, 2] - boxes[:, 0]
y_span = boxes[:, 3] - boxes[:, 1]
mhs = np.int(np.ceil(np.min([x_span.min(), y_span.min()]) / 2)) # min half span
mhs = int(np.ceil(np.min([x_span.min(), y_span.min()]) / 2)) # min half span
# Factorize image size - used for adaptive histogran
factors = []
for i in np.arange(2, mhs * 2):
Expand Down Expand Up @@ -217,8 +217,8 @@ def spider_web_network(im_soft, mhs, cc_im, dtheta=10, pixel_size=10.4e-6,
for l_int in np.arange(N):
x, y = np.where(lab == l_int + 1)
if len(x) > 0:
coords[l_int] = [np.int(np.around(np.mean(x), 0)),
np.int(np.around(np.mean(y), 0))]
coords[l_int] = [int(np.around(np.mean(x), 0)),
int(np.around(np.mean(y), 0))]
cc_coords.append(coords)
plt.figure()
plt.imshow(cc_im)
Expand Down Expand Up @@ -324,7 +324,7 @@ def spider_web_network(im_soft, mhs, cc_im, dtheta=10, pixel_size=10.4e-6,
coords_3d = np.vstack((new_coords[:, 0], new_coords[:, 1],
np.zeros(new_coords.shape[0]))).T
new_conns = np.vstack((neg_conns, pos_conns, neg_inner_conns, pos_inner_conns))
net = op.network.GenericNetwork(conns=new_conns, coords=coords_3d)
net = op.network.Network(conns=new_conns, coords=coords_3d)

Ps, counts = np.unique(np.hstack((net['throat.conns'][:, 0],
net['throat.conns'][:, 1])), return_counts=True)
Expand Down
Loading