From 9614c06da436137d7dcea4467382711bb0fdac32 Mon Sep 17 00:00:00 2001 From: LorenaH84 Date: Wed, 8 Apr 2026 17:26:26 +0200 Subject: [PATCH] Start on prismatic geometry --- docs/src/manuals/user_guide/geometries.md | 60 +++++++++++- docs/src/manuals/user_guide/pxd_model.md | 3 +- docs/src/manuals/user_guide/sub_models.md | 4 +- examples/example_3D_cylindrical.jl | 12 ++- examples/example_3d_prismatic.jl | 94 +++++++++++++++++++ src/BattMo.jl | 1 + src/grid/geometries/jelly_roll.jl | 76 ++++++++++++++- src/grid/geometries/prismatic.jl | 85 +++++++++++++++++ .../defaults/cell_parameters/chen_2020.json | 12 ++- .../defaults/cell_parameters/xu_2015.json | 2 +- .../model_settings/p4d_prismatic.json | 12 +++ .../simulation_settings/p4d_prismatic.json | 30 ++++++ src/input/equilibrium_kpis.jl | 17 +++- src/input/formatter.jl | 13 ++- src/input/meta_data/cell_parameters.jl | 28 +++++- src/input/meta_data/model_settings.jl | 4 +- src/input/schemas/get_schema.jl | 14 ++- .../schemas/lithium_ion/ModelSettings.json | 30 +++++- .../lithium_ion/SimulationSettings.json | 25 ++++- src/input/schemas/settings_meta_data.json | 5 +- src/models/formatter.jl | 13 ++- src/models/full_battery_models/battery.jl | 4 + src/models/full_battery_models/lithium_ion.jl | 2 + src/models/full_battery_models/sodium_ion.jl | 2 + src/output/output_format.jl | 4 +- 25 files changed, 511 insertions(+), 41 deletions(-) create mode 100644 examples/example_3d_prismatic.jl create mode 100644 src/grid/geometries/prismatic.jl create mode 100644 src/input/defaults/model_settings/p4d_prismatic.json create mode 100644 src/input/defaults/simulation_settings/p4d_prismatic.json diff --git a/docs/src/manuals/user_guide/geometries.md b/docs/src/manuals/user_guide/geometries.md index 6ec9bead3..9e248a9ca 100644 --- a/docs/src/manuals/user_guide/geometries.md +++ b/docs/src/manuals/user_guide/geometries.md @@ -6,6 +6,7 @@ The currently supported geometry families are: - `P2D` - `P4D Pouch` +- `P4D Prismatic` - `P4D Cylindrical` ## Overview @@ -14,6 +15,7 @@ The currently supported geometry families are: | --- | --- | --- | --- | | `P2D` | Through-thickness cell model | 1D across the stack, with radial particle diffusion | Fast cell-level studies, parameter sweeps, cycling analysis | | `P4D Pouch` | Pouch cell | 3D current-collector / electrode geometry combined with particle diffusion | Tab placement effects, current distribution, pouch thermal/electrical nonuniformity | +| `P4D Prismatic` | Wound prismatic cell | 3D jelly-roll geometry combined with particle diffusion | Wound prismatic tab placement studies and current distribution | | `P4D Cylindrical` | Cylindrical / jelly-roll cell | Cylindrical wound-cell geometry combined with particle diffusion | Radial and axial heterogeneity in cylindrical cells | In all three cases, BattMo uses a pseudo-dimensional formulation: @@ -140,6 +142,58 @@ See also: - [3D Pouch example](../../examples/example_3D_pouch.md) +## P4D Prismatic + +`P4D Prismatic` targets wound prismatic cells. It uses the same jelly-roll style internal geometry as the cylindrical model, but exposes it through a dedicated framework for prismatic cells packaged in a rectangular can. + +This geometry is useful when: + +- modelling wound prismatic commercial cells +- studying tab placement and collector-current distribution in a prismatic form factor +- comparing cylindrical and prismatic packaging while keeping the same wound internal structure + +Outputs are provided component-wise in the same way as for the cylindrical geometry. + +### Main Cell Parameters + +| Input path | Meaning | +| --- | --- | +| `["Cell"]["ElectrodeWidth"]` | Axial height of the jelly roll. | +| `["Cell"]["ElectrodeLength"]` | Winding length of the electrode stack used to determine the wound extent. | +| `["Cell"]["InnerRadius"]` | Inner jelly-roll radius. | +| `["Cell"]["CaseWidth"]` | Outer width of the invisible rectangular case used to flatten the wound cross-section. | +| `["Cell"]["CaseThickness"]` | Outer thickness of the invisible rectangular case used to flatten the wound cross-section. | +| `["Cell"]["OuterRadius"]` | Optional fallback outer radius if no winding length is provided. | +| `["NegativeElectrode"]["CurrentCollector"]["TabFractions"]` | Tab placement along the negative current collector as fractions of total spiral length. | +| `["PositiveElectrode"]["CurrentCollector"]["TabFractions"]` | Tab placement along the positive current collector as fractions of total spiral length. | +| `["NegativeElectrode"]["CurrentCollector"]["TabWidth"]` | Width of the negative current-collector tab. | +| `["PositiveElectrode"]["CurrentCollector"]["TabWidth"]` | Width of the positive current-collector tab. | +| `["NegativeElectrode"]["Coating"]["Thickness"]` | Negative electrode coating thickness. | +| `["PositiveElectrode"]["Coating"]["Thickness"]` | Positive electrode coating thickness. | +| `["Separator"]["Thickness"]` | Separator thickness. | +| `["NegativeElectrode"]["CurrentCollector"]["Thickness"]` | Negative current collector thickness. | +| `["PositiveElectrode"]["CurrentCollector"]["Thickness"]` | Positive current collector thickness. | + +### Main Simulation Settings + +| Setting | Meaning | +| --- | --- | +| `HeightGridPoints` | Number of grid cells along the prismatic-cell height. | +| `AngularGridPoints` | Number of angular sectors used around the wound internal geometry. | +| `NegativeElectrodeCoatingGridPoints` | Number of cells through the negative electrode coating thickness. | +| `PositiveElectrodeCoatingGridPoints` | Number of cells through the positive electrode coating thickness. | +| `SeparatorGridPoints` | Number of cells through the separator thickness. | +| `NegativeElectrodeParticleGridPoints` | Radial resolution inside negative active-material particles. | +| `PositiveElectrodeParticleGridPoints` | Radial resolution inside positive active-material particles. | +| `NegativeElectrodeCurrentCollectorGridPoints` | Number of cells through the negative current collector thickness. | +| `PositiveElectrodeCurrentCollectorGridPoints` | Number of cells through the positive current collector thickness. | +| `NegativeElectrodeCurrentCollectorTabWidthGridPoints` | Resolution across the negative tab face. | +| `PositiveElectrodeCurrentCollectorTabWidthGridPoints` | Resolution across the positive tab face. | + +See also: + +- [3D prismatic example](../../examples/example_3d_prismatic.md) + ## P4D Cylindrical `P4D Cylindrical` targets cylindrical cells with a jelly-roll type internal structure. It extends the pseudo-dimensional electrochemistry to a cylindrical geometry so that spatial variations around the wound cell can be represented. @@ -157,9 +211,10 @@ As for the pouch case, outputs are provided component-wise for the resolved doma | Input path | Meaning | | --- | --- | +| `["Cell"]["ElectrodeWidth"]` | Axial height of the jelly roll. | +| `["Cell"]["ElectrodeLength"]` | Winding length of the electrode stack used to determine the wound extent. | | `["Cell"]["InnerRadius"]` | Inner jelly-roll radius. | -| `["Cell"]["OuterRadius"]` | Outer jelly-roll radius. | -| `["Cell"]["Height"]` | Axial height of the cylindrical cell. | +| `["Cell"]["OuterRadius"]` | Optional fallback outer radius if no winding length is provided. | | `["NegativeElectrode"]["CurrentCollector"]["TabFractions"]` | Angular or spiral placement of one or more negative tabs as fractions of total spiral length. | | `["PositiveElectrode"]["CurrentCollector"]["TabFractions"]` | Angular or spiral placement of one or more positive tabs as fractions of total spiral length. | | `["NegativeElectrode"]["CurrentCollector"]["TabWidth"]` | Width of the negative current-collector tab. | @@ -198,6 +253,7 @@ As a rule of thumb: - use `P2D` when speed and robust cell-scale trends are most important - use `P4D Pouch` for pouch cells with spatially resolved collector/electrode effects +- use `P4D Prismatic` for wound prismatic cells with jelly-roll 3D effects - use `P4D Cylindrical` for cylindrical cells where wound-cell geometry matters You select the geometry through the model settings: diff --git a/docs/src/manuals/user_guide/pxd_model.md b/docs/src/manuals/user_guide/pxd_model.md index b69e86035..4c2b983e5 100644 --- a/docs/src/manuals/user_guide/pxd_model.md +++ b/docs/src/manuals/user_guide/pxd_model.md @@ -3,6 +3,7 @@ On this page, we describe the different models available in BattMo for simulation a lithium ion battery cell. We have models available for simulating 1D and 3D geometries: - P2D: Pseudo-two-dimensional model - P4D Pouch: Pseudo-four-dimensional model for pouch cells + - P4D Prismatic: Pseudo-four-dimensional model for prismatic cells - P4D Cylindrical: Pseudo-four-dimensional model for cylindrical cells ## P2D model @@ -185,4 +186,4 @@ This table lists all required parameters from the DFN model used in BattMo. -## P4D Cylindrical \ No newline at end of file +## P4D Cylindrical diff --git a/docs/src/manuals/user_guide/sub_models.md b/docs/src/manuals/user_guide/sub_models.md index 6ff71815e..0eb965266 100644 --- a/docs/src/manuals/user_guide/sub_models.md +++ b/docs/src/manuals/user_guide/sub_models.md @@ -5,7 +5,7 @@ | ModelSetting | Sub-model(s) | Description | |-----------------------------------|-------------------------------------------|-----------------------------------------------------------------------| -| `"ModelFrameWork"` | "P2D", "P4D Pouch", "P4D Cylindrical" | [See the PXD section](../user_guide/pxd_model.md) | +| `"ModelFrameWork"` | "P2D", "P4D Pouch", "P4D Prismatic", "P4D Cylindrical" | [See the PXD section](../user_guide/pxd_model.md) | | `"TransportInSolid"` | "FullDiffusion" | - | | `"RampUp"` | "Sinusoidal" | [See the Ramp Up section](../user_guide/ramp_up.md) | | `"ButlerVolmer"` | "Standard", "Chayambuka" | [See the Sodium ion section](../user_guide/sodium_ion_model.md) | @@ -43,4 +43,4 @@ The model settings can then be pased to the battery model struct that you'd like ``` model = LithiumIonBattery(;model_settings = model_settings) -``` \ No newline at end of file +``` diff --git a/examples/example_3D_cylindrical.jl b/examples/example_3D_cylindrical.jl index d95ab28aa..792ea72c8 100644 --- a/examples/example_3D_cylindrical.jl +++ b/examples/example_3D_cylindrical.jl @@ -19,8 +19,12 @@ nothing #hide # ## Review and modify the cell parameters # We go through some of the geometrical and discretization parameters. We modify some of them to obtain a cell where the different components are easier to visualize -# The cell geometry is determined by the inner and outer radius and the height. We reduce the outer radius -cell_parameters["Cell"]["OuterRadius"] = 0.01 +# The wound geometry is determined primarily by the inner radius, the winding +# length and the jelly-roll height. We shorten the winding length here to make +# the geometry more compact. +cell_parameters["Cell"]["ElectrodeWidth"] = 0.065 +cell_parameters["Cell"]["ElectrodeLength"] = 1.6 +cell_parameters["Cell"]["InnerRadius"] = 2e-3 nothing #hide # We modify the current collector thicknesses, for visualization purpose @@ -125,7 +129,9 @@ simulation_settings = load_simulation_settings(; from_default_set = "p4d_cylindr # We adjust the parameters so that the simulation in this example is not too long (around a couple of minutes) -cell_parameters["Cell"]["OuterRadius"] = 0.004 +cell_parameters["Cell"]["ElectrodeWidth"] = 0.065 +cell_parameters["Cell"]["ElectrodeLength"] = 0.55 +cell_parameters["Cell"]["InnerRadius"] = 2e-3 cell_parameters["NegativeElectrode"]["CurrentCollector"]["TabFractions"] = [0.5] cell_parameters["PositiveElectrode"]["CurrentCollector"]["TabFractions"] = [0.5] cell_parameters["NegativeElectrode"]["CurrentCollector"]["TabWidth"] = 0.002 diff --git a/examples/example_3d_prismatic.jl b/examples/example_3d_prismatic.jl new file mode 100644 index 000000000..5ea76edbe --- /dev/null +++ b/examples/example_3d_prismatic.jl @@ -0,0 +1,94 @@ +# # 3D wound prismatic cell geometry and plotting +# +# This example demonstrates how to set up, run and visualize a wound prismatic +# battery model. The internal geometry is a jelly roll, like the cylindrical +# model, but it is exposed through the `P4D Prismatic` framework so the setup +# can represent a wound cell packaged in a prismatic form factor. + +using BattMo, Jutul, GLMakie + +# ## Load parameter sets + +cell_parameters = load_cell_parameters(; from_default_set = "chen_2020") +cycling_protocol = load_cycling_protocol(; from_default_set = "cc_discharge") +model_settings = load_model_settings(; from_default_set = "p4d_prismatic") +simulation_settings = load_simulation_settings(; from_default_set = "p4d_prismatic") +nothing #hide + +# ## Adjust the geometry + +# A more compact winding length and thicker current collectors make the wound +# structure easier to inspect visually. +cell_parameters["Cell"]["Case"] = "Prismatic" +cell_parameters["Cell"]["ElectrodeWidth"] = 0.065 +cell_parameters["Cell"]["ElectrodeLength"] = 0.5 +cell_parameters["Cell"]["InnerRadius"] = 2e-3 +cell_parameters["Cell"]["CaseWidth"] = 0.03 +cell_parameters["Cell"]["CaseThickness"] = 0.010 +cell_parameters["NegativeElectrode"]["CurrentCollector"]["Thickness"] = 50.0e-6 +cell_parameters["PositiveElectrode"]["CurrentCollector"]["Thickness"] = 50.0e-6 + +# Tabs are specified as fractions of the total spiral length of each current +# collector, which is the natural coordinate for wound-cell tab placement. +cell_parameters["NegativeElectrode"]["CurrentCollector"]["TabFractions"] = [0.2, 0.5, 0.8] +cell_parameters["PositiveElectrode"]["CurrentCollector"]["TabFractions"] = [0.2, 0.5, 0.8] +cell_parameters["NegativeElectrode"]["CurrentCollector"]["TabWidth"] = 0.002 +cell_parameters["PositiveElectrode"]["CurrentCollector"]["TabWidth"] = 0.002 + +simulation_settings["AngularGridPoints"] = 30 +nothing #hide + +# ## Create the simulation object + +model = LithiumIonBattery(; model_settings) +sim = Simulation(model, cell_parameters, cycling_protocol; simulation_settings) + +grids = sim.grids +couplings = sim.couplings +nothing #hide + +# ## Visualize the wound component meshes + +components = ["NegativeElectrode", "PositiveElectrode", "NegativeCurrentCollector", "PositiveCurrentCollector"] +colors = [:gray, :green, :blue, :black] +nothing #hide + +for (i, component) in enumerate(components) + if i == 1 + global fig, ax = plot_mesh(grids[component], color = colors[i]) + else + plot_mesh!(ax, grids[component], color = colors[i]) + end + plot_mesh_edges!(ax, grids[component], color = :black, linewidth = 0.6) +end +ax.aspect = :data +ax.azimuth[] = 5.0 +ax.elevation[] = 0.35 +display(GLMakie.Screen(), fig) +fig #hide + +# ## Highlight the tab couplings + +for component in ["NegativeCurrentCollector", "PositiveCurrentCollector"] + plot_mesh!( + ax, grids[component]; + boundaryfaces = couplings[component]["External"]["boundaryfaces"], + color = :red, + ) +end + +ax.aspect = :data +ax.azimuth[] = 5.0 +ax.elevation[] = 0.35 +display(GLMakie.Screen(), fig) +fig #hide + +# # ## Run a compact simulation +# output = solve(sim; info_level = -1) +# nothing #hide + +# # ## Visualize the simulation output + +# plot_dashboard(output; plot_type = "simple") + +# plot_interactive_3d(output) diff --git a/src/BattMo.jl b/src/BattMo.jl index ade2a292a..6eb8e349d 100644 --- a/src/BattMo.jl +++ b/src/BattMo.jl @@ -259,6 +259,7 @@ include("grid/grid_conversion.jl") include("grid/grid_utils.jl") include("grid/geometries/1d.jl") include("grid/geometries/pouch.jl") +include("grid/geometries/prismatic.jl") include("grid/geometries/jelly_roll.jl") include("solver/solver_as_preconditioner_system.jl") diff --git a/src/grid/geometries/jelly_roll.jl b/src/grid/geometries/jelly_roll.jl index 1fbecc89a..a466c2f0f 100644 --- a/src/grid/geometries/jelly_roll.jl +++ b/src/grid/geometries/jelly_roll.jl @@ -4,6 +4,73 @@ export jelly_roll_grid # jelly roll grid setup # ######################### +function get_jelly_roll_stack_thickness(cell_parameters) + dxs = [ + cell_parameters["PositiveElectrode"]["Coating"]["Thickness"], + cell_parameters["PositiveElectrode"]["CurrentCollector"]["Thickness"], + cell_parameters["PositiveElectrode"]["Coating"]["Thickness"], + cell_parameters["Separator"]["Thickness"], + cell_parameters["NegativeElectrode"]["Coating"]["Thickness"], + cell_parameters["NegativeElectrode"]["CurrentCollector"]["Thickness"], + cell_parameters["NegativeElectrode"]["Coating"]["Thickness"], + cell_parameters["Separator"]["Thickness"], + ] + return sum(dxs) +end + +function jelly_roll_arc_length(outer_radius, inner_radius, stack_thickness) + A = stack_thickness / (2 * pi) + u0 = inner_radius + u1 = outer_radius + F(u) = u * sqrt(u^2 + A^2) + A^2 * asinh(u / A) + return (F(u1) - F(u0)) / (2 * A) +end + +function outer_radius_from_winding_length(winding_length, inner_radius, stack_thickness) + @assert winding_length > 0.0 "ElectrodeLength must be positive." + @assert inner_radius > 0.0 "InnerRadius must be positive." + @assert stack_thickness > 0.0 "Cell stack thickness must be positive." + + lo = inner_radius + hi = inner_radius + stack_thickness + while jelly_roll_arc_length(hi, inner_radius, stack_thickness) < winding_length + hi *= 2 + end + + for _ in 1:80 + mid = 0.5 * (lo + hi) + if jelly_roll_arc_length(mid, inner_radius, stack_thickness) < winding_length + lo = mid + else + hi = mid + end + end + return hi +end + +function get_jelly_roll_dimensions(cell_parameters) + cell = cell_parameters["Cell"] + inner_radius = cell["InnerRadius"] + height = get(cell, "ElectrodeWidth", get(cell, "Height", nothing)) + if isnothing(height) + error("Jelly-roll geometries require Cell/ElectrodeWidth, or Cell/Height as a fallback.") + end + + stack_thickness = get_jelly_roll_stack_thickness(cell_parameters) + winding_length = get(cell, "ElectrodeLength", nothing) + + outer_radius = + if !isnothing(winding_length) + outer_radius_from_winding_length(winding_length, inner_radius, stack_thickness) + elseif haskey(cell, "OuterRadius") + cell["OuterRadius"] + else + error("Jelly-roll geometries require Cell/ElectrodeLength, or Cell/OuterRadius as a fallback.") + end + + return (inner_radius = inner_radius, outer_radius = outer_radius, height = height, stack_thickness = stack_thickness) +end + function jelly_roll_grid(input) cell_parameters = input.cell_parameters @@ -13,9 +80,10 @@ function jelly_roll_grid(input) nangles = simulation_settings["AngularGridPoints"] nz = simulation_settings["HeightGridPoints"] - rinner = cell["InnerRadius"] - router = cell["OuterRadius"] - height = cell["Height"] + dims = get_jelly_roll_dimensions(cell_parameters) + rinner = dims.inner_radius + router = dims.outer_radius + height = dims.height function get_vector(geomparams, fdname) # double coated electrode @@ -57,7 +125,7 @@ function jelly_roll_grid(input) spacing = [0; cumsum(dx)] spacing = spacing / spacing[end] - thickness = sum(dxs) + thickness = dims.stack_thickness depths = [0; cumsum(repeat([height / nz], nz))] diff --git a/src/grid/geometries/prismatic.jl b/src/grid/geometries/prismatic.jl new file mode 100644 index 000000000..e2dc4659c --- /dev/null +++ b/src/grid/geometries/prismatic.jl @@ -0,0 +1,85 @@ +export prismatic_grid + +""" +Create a wound 3D prismatic-cell grid. + +The prismatic implementation reuses the jelly-roll internal geometry used for +cylindrical cells and then deforms the x-y coordinates into a rounded +rectangular cross-section. This represents a wound electrode stack packaged in a +prismatic form factor. +""" +function prismatic_grid(input) + grids, couplings = jelly_roll_grid(input) + + cell = input.cell_parameters["Cell"] + dims = get_jelly_roll_dimensions(input.cell_parameters) + outer_radius = dims.outer_radius + case_width = cell["CaseWidth"] + case_thickness = cell["CaseThickness"] + + deformed_grids = Dict{String, Any}() + for (name, grid) in grids + if grid isa UnstructuredMesh + deformed_grids[name] = deform_wound_grid_to_prismatic( + grid, + outer_radius, + case_width, + case_thickness, + ) + else + deformed_grids[name] = grid + end + end + + return deformed_grids, couplings +end + +function deform_wound_grid_to_prismatic( + grid::UnstructuredMesh, + outer_radius::Real, + case_width::Real, + case_thickness::Real; + exponent::Real = 6.0, +) + a = 0.5 * case_width + b = 0.5 * case_thickness + + @assert a > 0.0 "CaseWidth must be positive." + @assert b > 0.0 "CaseThickness must be positive." + @assert outer_radius > 0.0 "OuterRadius must be positive." + + function superellipse_radius(theta) + ct = abs(cos(theta)) + st = abs(sin(theta)) + return ((ct / a)^exponent + (st / b)^exponent)^(-1 / exponent) + end + + new_node_points = map(grid.node_points) do pt + x = pt[1] + y = pt[2] + r = hypot(x, y) + if iszero(r) + return pt + end + theta = atan(y, x) + target_radius = superellipse_radius(theta) + scale = target_radius / outer_radius + return SVector(x * scale, y * scale, pt[3]) + end + + return UnstructuredMesh( + grid.faces.cells_to_faces, + grid.boundary_faces.cells_to_faces, + grid.faces.faces_to_nodes, + grid.boundary_faces.faces_to_nodes, + new_node_points, + grid.faces.neighbors, + grid.boundary_faces.neighbors; + cell_map = grid.cell_map, + face_map = grid.face_map, + boundary_map = grid.boundary_map, + node_map = grid.node_map, + structure = grid.structure, + z_is_depth = grid.z_is_depth, + ) +end diff --git a/src/input/defaults/cell_parameters/chen_2020.json b/src/input/defaults/cell_parameters/chen_2020.json index e018124d5..7831580df 100644 --- a/src/input/defaults/cell_parameters/chen_2020.json +++ b/src/input/defaults/cell_parameters/chen_2020.json @@ -4,7 +4,11 @@ "Source": "https://doi.org/10.1149/1945-7111/ab9050", "Description": "Parameter set of a cylindrical 21700 commercial cell (LGM50), for an electrochemical pseudo-two-dimensional (P2D) model, after calibration. SEI parameters are from Bolay2022: https://doi.org/10.1016/j.powera.2022.100083 .", "Models": { - "ModelFramework": "P2D", + "ModelFramework": [ + "P2D", + "P4D Cylindrical", + "P4D Prismatic" + ], "TransportInSolid": "FullDiffusion", "CurrentCollectors": "Generic", "RampUp": "Sinusoidal", @@ -15,8 +19,12 @@ "Name": "LG INR 21700 M50", "Case": "Cylindrical", "Height": 0.065, + "ElectrodeWidth": 0.065, + "ElectrodeLength": 3.6748, "OuterRadius": 0.021, "InnerRadius": 2e-3, + "CaseWidth": 0.036, + "CaseThickness": 0.014, "ElectrodeGeometricSurfaceArea": 0.1027, "NominalVoltage": 3.71, "NominalCapacity": 4.8 @@ -141,4 +149,4 @@ "IonicConductivity": "0.1297*(c/1000)^3 - 2.51*(c/1000)^(1.5) + 3.329*(c/1000)", "DiffusionCoefficient": "8.794*10^(-11)*(c/1000)^2 - 3.972*10^(-10)*(c/1000) + 4.862*10^(-10)" } -} \ No newline at end of file +} diff --git a/src/input/defaults/cell_parameters/xu_2015.json b/src/input/defaults/cell_parameters/xu_2015.json index d474857b5..c9bf46274 100644 --- a/src/input/defaults/cell_parameters/xu_2015.json +++ b/src/input/defaults/cell_parameters/xu_2015.json @@ -137,4 +137,4 @@ } } -} \ No newline at end of file +} diff --git a/src/input/defaults/model_settings/p4d_prismatic.json b/src/input/defaults/model_settings/p4d_prismatic.json new file mode 100644 index 000000000..3a1900394 --- /dev/null +++ b/src/input/defaults/model_settings/p4d_prismatic.json @@ -0,0 +1,12 @@ +{ + "Metadata": { + "Title": "p4d_prismatic", + "Description": "Default model settings for a P4D prismatic simulation including a current ramp up and current collectors." + }, + "ModelFramework": "P4D Prismatic", + "TransportInSolid": "FullDiffusion", + "PotentialFlowDiscretization": "GeneralAD", + "ButlerVolmer": "Standard", + "CurrentCollectors": "Standard", + "RampUp": "Sinusoidal" +} diff --git a/src/input/defaults/simulation_settings/p4d_prismatic.json b/src/input/defaults/simulation_settings/p4d_prismatic.json new file mode 100644 index 000000000..231ae608d --- /dev/null +++ b/src/input/defaults/simulation_settings/p4d_prismatic.json @@ -0,0 +1,30 @@ +{ + "Metadata": { + "Title": "p4d_prismatic", + "Description": "Default simulation settings for a P4D prismatic simulation including a current ramp up and current collectors.", + "Models": { + "ModelFramework": [ + "P2D", + "P4D Prismatic" + ], + "TransportInSolid": "FullDiffusion", + "CurrentCollector": "Generic", + "RampUp": "Sinusoidal", + "SEIModel": "Bolay" + } + }, + "HeightGridPoints": 2, + "AngularGridPoints": 8, + "PositiveElectrodeCoatingGridPoints": 3, + "PositiveElectrodeParticleGridPoints": 5, + "PositiveElectrodeCurrentCollectorGridPoints": 3, + "PositiveElectrodeCurrentCollectorTabWidthGridPoints": 2, + "NegativeElectrodeCoatingGridPoints": 3, + "NegativeElectrodeParticleGridPoints": 5, + "NegativeElectrodeCurrentCollectorGridPoints": 3, + "NegativeElectrodeCurrentCollectorTabWidthGridPoints": 2, + "SeparatorGridPoints": 3, + "TimeStepDuration": 50, + "RampUpTime": 10, + "RampUpSteps": 5 +} diff --git a/src/input/equilibrium_kpis.jl b/src/input/equilibrium_kpis.jl index 4cd50e287..54bd05a8b 100644 --- a/src/input/equilibrium_kpis.jl +++ b/src/input/equilibrium_kpis.jl @@ -245,14 +245,27 @@ function compute_cell_volume(params::CellParameters) volume = area * thickness elseif case == "Cylindrical" - if haskey(params["Cell"], "Height") && haskey(params["Cell"], "OuterRadius") + if haskey(params["Cell"], "ElectrodeWidth") && haskey(params["Cell"], "OuterRadius") + height = params["Cell"]["ElectrodeWidth"] + radius = params["Cell"]["OuterRadius"] + + volume = pi * radius^2 * height + elseif haskey(params["Cell"], "Height") && haskey(params["Cell"], "OuterRadius") height = params["Cell"]["Height"] radius = params["Cell"]["OuterRadius"] volume = pi * radius^2 * height else volume = nothing - println("Parameter set doesn't contain the required parameters to calculate the volume: ['Cell']['Height'] and ['Cell']['OuterRadius']") + println("Parameter set doesn't contain the required parameters to calculate the volume: ['Cell']['ElectrodeWidth'] or ['Cell']['Height'], and ['Cell']['OuterRadius']") + end + + elseif case == "Prismatic" + if haskey(params["Cell"], "ElectrodeWidth") && haskey(params["Cell"], "CaseWidth") && haskey(params["Cell"], "CaseThickness") + volume = params["Cell"]["ElectrodeWidth"] * params["Cell"]["CaseWidth"] * params["Cell"]["CaseThickness"] + else + volume = nothing + println("Parameter set doesn't contain the required parameters to calculate the volume: ['Cell']['ElectrodeWidth'], ['Cell']['CaseWidth'] and ['Cell']['CaseThickness']") end diff --git a/src/input/formatter.jl b/src/input/formatter.jl index 453d40ae6..cae16fdbf 100644 --- a/src/input/formatter.jl +++ b/src/input/formatter.jl @@ -89,10 +89,13 @@ function convert_to_parameter_sets(params::AdvancedDictInput) elseif params["Geometry"]["case"] == "3D-demo" || params["Geometry"]["case"] == "multiLayerPouch" geom = "P4D Pouch" + elseif params["Geometry"]["case"] == "prismatic" + geom = "P4D Prismatic" + elseif params["Geometry"]["case"] == "jellyRoll" geom = "P4D Cylindrical" else - error("ModelFramework not recognized. Please use '1D', '3D-demo', 'multiLayerPouch' or 'jellyRoll'.") + error("ModelFramework not recognized. Please use '1D', '3D-demo', 'multiLayerPouch', 'prismatic' or 'jellyRoll'.") end @@ -134,7 +137,7 @@ function convert_to_parameter_sets(params::AdvancedDictInput) simulation_settings["RampUpSteps"] = params["TimeStepping"]["numberOfRampupSteps"] end - if model_settings["ModelFramework"] == "P4D Cylindrical" + if model_settings["ModelFramework"] == "P4D Cylindrical" || model_settings["ModelFramework"] == "P4D Prismatic" simulation_settings["HeightGridPoints"] = params["Geometry"]["numberOfDiscretizationCellsVertical"] simulation_settings["AngularGridPoints"] = params["Geometry"]["numberOfDiscretizationCellsAngular"] @@ -401,7 +404,7 @@ function convert_to_parameter_sets(params::AdvancedDictInput) end end - elseif model_settings["ModelFramework"] == "P4D Cylindrical" + elseif model_settings["ModelFramework"] == "P4D Cylindrical" || model_settings["ModelFramework"] == "P4D Prismatic" cell_parameters["Cell"]["Height"] = params["Geometry"]["height"] cell_parameters["Cell"]["InnerRadius"] = params["Geometry"]["innerRadius"] cell_parameters["Cell"]["OuterRadius"] = params["Geometry"]["outerRadius"] @@ -532,11 +535,13 @@ function convert_parameter_sets_to_old_input_format(model_settings::ModelSetting geom_case = "1D" elseif geom == "P4D Pouch" geom_case = "3D-demo" + elseif geom == "P4D Prismatic" + geom_case = "prismatic" elseif geom == "P4D Cylindrical" geom_case = "jellyRoll" else - error("ModelFramework $geom not recognized. Please use 'P2D', 'P4D Pouch' or 'P4D Cylindrical'.") + error("ModelFramework $geom not recognized. Please use 'P2D', 'P4D Pouch', 'P4D Prismatic' or 'P4D Cylindrical'.") end end diff --git a/src/input/meta_data/cell_parameters.jl b/src/input/meta_data/cell_parameters.jl index 323e6082c..18eb8523e 100644 --- a/src/input/meta_data/cell_parameters.jl +++ b/src/input/meta_data/cell_parameters.jl @@ -5,10 +5,10 @@ function get_cell_parameters_meta_data() meta_data = Dict( "Case" => Dict( "type" => String, - "options" => ["Cylindrical", "Pouch"], + "options" => ["Cylindrical", "Pouch", "Prismatic"], "context_type" => "hasCase", "context_type_iri" => "https://w3id.org/emmo/domain/electrochemistry#electrochemistry_3dcfe33d_6825_43c0_a798_68e871a68d39", - "description" => "Type of physical container encapsulating the electrochemical cell. Examples: Cylindrical, Pouch.", + "description" => "Type of physical container encapsulating the electrochemical cell. Examples: Cylindrical, Pouch, Prismatic.", ), "EffectiveDensity" => Dict( "type" => Real, @@ -69,6 +69,28 @@ function get_cell_parameters_meta_data() "context_type" => "OuterRadius", "description" => "Radius of the rolled electrodes within a cylindrical cell, measured from the center of the cell to the outer side of the cell wall.", ), + "CaseWidth" => Dict( + "type" => Real, + "min_value" => 0.001, + "max_value" => 0.2, + "unit" => "m", + "unit_name" => "emmo:Metre", + "unit_iri" => "https://w3id.org/emmo#Metre", + "context_type" => "Width", + "context_type_iri" => "https://w3id.org/emmo#EMMO_e4de48b1_dabb_4490_ac2b_040f926c64f0", + "description" => "Outer width of the invisible rectangular case used to deform a wound prismatic cell cross-section.", + ), + "CaseThickness" => Dict( + "type" => Real, + "min_value" => 0.001, + "max_value" => 0.2, + "unit" => "m", + "unit_name" => "emmo:Metre", + "unit_iri" => "https://w3id.org/emmo#Metre", + "context_type" => "Thickness", + "context_type_iri" => "https://w3id.org/emmo#EMMO_43003c86_9d15_433b_9789_ee2940920656", + "description" => "Outer thickness of the invisible rectangular case used to deform a wound prismatic cell cross-section.", + ), "ElectrodeWidth" => Dict( "type" => Real, "min_value" => 0.01, @@ -83,7 +105,7 @@ function get_cell_parameters_meta_data() "ElectrodeLength" => Dict( "type" => Real, "min_value" => 0.01, - "max_value" => 0.5, + "max_value" => 2.0, "unit" => "m", "unit_name" => "emmo:Metre", "unit_iri" => "https://w3id.org/emmo#Metre", diff --git a/src/input/meta_data/model_settings.jl b/src/input/meta_data/model_settings.jl index cdd6a2e80..37c95d9c0 100644 --- a/src/input/meta_data/model_settings.jl +++ b/src/input/meta_data/model_settings.jl @@ -5,12 +5,12 @@ function get_model_settings_meta_data() meta_data = Dict( "ModelFramework" => Dict( "type" => String, - "options" => ["P2D", "P4D Pouch", "P4D Cylindrical"], + "options" => ["P2D", "P4D Pouch", "P4D Prismatic", "P4D Cylindrical"], "context_type" => "ModelFramework", "context_type_iri" => "https://w3id.org/emmo/domain/battery#battery_b1921f7b_afac_465a_a275_26f929f7f936", "category" => "ModelSettings", "documentation" => "https://battmoteam.github.io/BattMo.jl/dev/manuals/user_guide/pxd_model", - "description" => """Framework defining the dimensionality of the electrochemical model. Examples: "P2D", "P4D Pouch". """, + "description" => """Framework defining the dimensionality of the electrochemical model. Examples: "P2D", "P4D Pouch", "P4D Prismatic". """, ), "SEIModel" => Dict( "type" => String, diff --git a/src/input/schemas/get_schema.jl b/src/input/schemas/get_schema.jl index 3eb48f5af..b21bae507 100644 --- a/src/input/schemas/get_schema.jl +++ b/src/input/schemas/get_schema.jl @@ -123,6 +123,8 @@ function get_schema_cell_parameters(model_settings::ModelSettings) "DeviceSurfaceArea" => create_property(parameter_meta, "DeviceSurfaceArea"), "InnerRadius" => create_property(parameter_meta, "InnerRadius"), "OuterRadius" => create_property(parameter_meta, "OuterRadius"), + "CaseWidth" => create_property(parameter_meta, "CaseWidth"), + "CaseThickness" => create_property(parameter_meta, "CaseThickness"), "NominalVoltage" => create_property(parameter_meta, "NominalVoltage"), "NominalCapacity" => create_property(parameter_meta, "NominalCapacity"), "HeatTransferCoefficient" => create_property(parameter_meta, "HeatTransferCoefficient"), @@ -382,11 +384,15 @@ function get_schema_cell_parameters(model_settings::ModelSettings) end - elseif model_settings["ModelFramework"] == "P4D Cylindrical" + elseif model_settings["ModelFramework"] == "P4D Cylindrical" || model_settings["ModelFramework"] == "P4D Prismatic" + push!(cell_required, "ElectrodeWidth") push!(cell_required, "InnerRadius") - push!(cell_required, "OuterRadius") - push!(cell_required, "Height") + if model_settings["ModelFramework"] == "P4D Prismatic" + push!(cell_required, "ElectrodeLength") + push!(cell_required, "CaseWidth") + push!(cell_required, "CaseThickness") + end if haskey(model_settings, "CurrentCollectors") push!(ne_required, "CurrentCollector") @@ -618,7 +624,7 @@ function get_schema_simulation_settings(model_settings) push!(required, "RampUpSteps") end - if model_settings["ModelFramework"] == "P4D Cylindrical" + if model_settings["ModelFramework"] == "P4D Cylindrical" || model_settings["ModelFramework"] == "P4D Prismatic" push!(required, "HeightGridPoints") push!(required, "AngularGridPoints") if haskey(model_settings, "CurrentCollectors") diff --git a/src/input/schemas/lithium_ion/ModelSettings.json b/src/input/schemas/lithium_ion/ModelSettings.json index b2d159fb3..6758f263e 100644 --- a/src/input/schemas/lithium_ion/ModelSettings.json +++ b/src/input/schemas/lithium_ion/ModelSettings.json @@ -8,9 +8,10 @@ "enum": [ "P2D", "P4D Pouch", + "P4D Prismatic", "P4D Cylindrical" ], - "description": "Framework defining the dimensionality of the electrochemical model. Examples: \"P2D\", \"P4D Pouch\"." + "description": "Framework defining the dimensionality of the electrochemical model. Examples: \"P2D\", \"P4D Pouch\", \"P4D Prismatic\"." }, "SEIModel": { "type": "string", @@ -76,6 +77,20 @@ ] } }, + { + "if": { + "properties": { + "ModelFramework": { + "const": "P4D Prismatic" + } + } + }, + "then": { + "required": [ + "CurrentCollectors" + ] + } + }, { "if": { "properties": { @@ -114,6 +129,17 @@ "ModelFramework" ] }, + "useP4DPrismatic": { + "type": "object", + "properties": { + "ModelFramework": { + "const": "P4D Prismatic" + } + }, + "required": [ + "ModelFramework" + ] + }, "useP4DCylindrical": { "type": "object", "properties": { @@ -170,4 +196,4 @@ ] } } -} \ No newline at end of file +} diff --git a/src/input/schemas/lithium_ion/SimulationSettings.json b/src/input/schemas/lithium_ion/SimulationSettings.json index 023b63fe8..95795481f 100644 --- a/src/input/schemas/lithium_ion/SimulationSettings.json +++ b/src/input/schemas/lithium_ion/SimulationSettings.json @@ -157,6 +157,29 @@ "ModelSettingsGridPoints" ], "allOf": [ + { + "if": { + "allof": [ + { + "$ref": "https://example.com/ModelSettings.json#/$defs/useCurrentCollectors" + }, + { + "$ref": "https://example.com/ModelSettings.json#/$defs/useP4DPrismatic" + } + ] + }, + "then": { + "required": [ + "HeightGridPoints", + "AngularGridPoints", + "PositiveElectrodeCurrentCollectorGridPoints", + "PositiveElectrodeCurrentCollectorTabWidthGridPoints", + "NegativeElectrodeCurrentCollectorGridPoints", + "NegativeElectrodeCurrentCollectorTabWidthGridPoints", + "SeparatorGridPoints" + ] + } + }, { "if": { "allof": [ @@ -216,4 +239,4 @@ } } ] -} \ No newline at end of file +} diff --git a/src/input/schemas/settings_meta_data.json b/src/input/schemas/settings_meta_data.json index 68c6d6642..cd5b2c6d1 100644 --- a/src/input/schemas/settings_meta_data.json +++ b/src/input/schemas/settings_meta_data.json @@ -8,9 +8,10 @@ "enum": [ "P2D", "P4D Pouch", + "P4D Prismatic", "P4D Cylindrical" ], - "description": "Framework defining the dimensionality of the electrochemical model. Examples: \"P2D\", \"P4D Pouch\"." + "description": "Framework defining the dimensionality of the electrochemical model. Examples: \"P2D\", \"P4D Pouch\", \"P4D Prismatic\"." }, "SEIModel": { "type": "string", @@ -173,4 +174,4 @@ }, "required": [], "additionalProperties": true -} \ No newline at end of file +} diff --git a/src/models/formatter.jl b/src/models/formatter.jl index abdbc0246..fa5bde0ee 100644 --- a/src/models/formatter.jl +++ b/src/models/formatter.jl @@ -77,10 +77,13 @@ function convert_to_parameter_sets(params::AdvancedDictInput) elseif params["Geometry"]["case"] == "3D-demo" || params["Geometry"]["case"] == "multiLayerPouch" geom = "P4D Pouch" + elseif params["Geometry"]["case"] == "prismatic" + geom = "P4D Prismatic" + elseif params["Geometry"]["case"] == "jellyRoll" geom = "P4D Cylindrical" else - error("ModelFramework not recognized. Please use '1D', '3D-demo' or 'jellyRoll'.") + error("ModelFramework not recognized. Please use '1D', '3D-demo', 'prismatic' or 'jellyRoll'.") end @@ -122,7 +125,7 @@ function convert_to_parameter_sets(params::AdvancedDictInput) simulation_settings["RampUpSteps"] = params["TimeStepping"]["numberOfRampupSteps"] end - if model_settings["ModelFramework"] == "P4D Cylindrical" + if model_settings["ModelFramework"] == "P4D Cylindrical" || model_settings["ModelFramework"] == "P4D Prismatic" simulation_settings["HeightGridPoints"] = params["Geometry"]["numberOfDiscretizationCellsVertical"] simulation_settings["AngularGridPoints"] = params["Geometry"]["numberOfDiscretizationCellsAngular"] @@ -389,7 +392,7 @@ function convert_to_parameter_sets(params::AdvancedDictInput) end end - elseif model_settings["ModelFramework"] == "P4D Cylindrical" + elseif model_settings["ModelFramework"] == "P4D Cylindrical" || model_settings["ModelFramework"] == "P4D Prismatic" cell_parameters["Cell"]["Height"] = params["Geometry"]["height"] cell_parameters["Cell"]["InnerRadius"] = params["Geometry"]["innerRadius"] cell_parameters["Cell"]["OuterRadius"] = params["Geometry"]["outerRadius"] @@ -520,11 +523,13 @@ function convert_parameter_sets_to_old_input_format(model_settings::ModelSetting geom_case = "1D" elseif geom == "P4D Pouch" geom_case = "3D-demo" + elseif geom == "P4D Prismatic" + geom_case = "prismatic" elseif geom == "P4D Cylindrical" geom_case = "jellyRoll" else - error("ModelFramework $geom not recognized. Please use 'P2D', 'P4D Pouch' or 'P4D Cylindrical'.") + error("ModelFramework $geom not recognized. Please use 'P2D', 'P4D Pouch', 'P4D Prismatic' or 'P4D Cylindrical'.") end end diff --git a/src/models/full_battery_models/battery.jl b/src/models/full_battery_models/battery.jl index dfc5ea15a..08f48eb0d 100644 --- a/src/models/full_battery_models/battery.jl +++ b/src/models/full_battery_models/battery.jl @@ -59,6 +59,10 @@ function setup_grids_and_couplings(model::M, input) where {M <: Battery} grids, couplings = pouch_grid(input) + elseif case_type == "P4D Prismatic" + + grids, couplings = prismatic_grid(input) + elseif case_type == "P4D Cylindrical" grids, couplings = jelly_roll_grid(input) diff --git a/src/models/full_battery_models/lithium_ion.jl b/src/models/full_battery_models/lithium_ion.jl index ce7a7587a..0279cea9a 100644 --- a/src/models/full_battery_models/lithium_ion.jl +++ b/src/models/full_battery_models/lithium_ion.jl @@ -59,6 +59,8 @@ function get_default_simulation_settings(model::LithiumIonBattery) settings = load_simulation_settings(; from_default_set = "p2d") elseif model_framework == "P4D Pouch" settings = load_simulation_settings(; from_default_set = "p4d_pouch") + elseif model_framework == "P4D Prismatic" + settings = load_simulation_settings(; from_default_set = "p4d_prismatic") elseif model_framework == "P4D Cylindrical" settings = load_simulation_settings(; from_default_set = "p4d_cylindrical") else diff --git a/src/models/full_battery_models/sodium_ion.jl b/src/models/full_battery_models/sodium_ion.jl index 7e57848c7..832e126b4 100644 --- a/src/models/full_battery_models/sodium_ion.jl +++ b/src/models/full_battery_models/sodium_ion.jl @@ -56,6 +56,8 @@ function get_default_simulation_settings(model::SodiumIonBattery) settings = load_simulation_settings(; from_default_set = "p2d_fine_resolution") elseif model_framework == "P4D Pouch" settings = load_simulation_settings(; from_default_set = "p4d_pouch") + elseif model_framework == "P4D Prismatic" + settings = load_simulation_settings(; from_default_set = "p4d_prismatic") elseif model_framework == "P4D Cylindrical" settings = load_simulation_settings(; from_default_set = "p4d_cylindrical") else diff --git a/src/output/output_format.jl b/src/output/output_format.jl index acd7822c2..4f37e292b 100644 --- a/src/output/output_format.jl +++ b/src/output/output_format.jl @@ -241,7 +241,7 @@ function get_output_states( available_quantities[k] = v end - elseif input["ModelSettings"]["ModelFramework"] == "P4D Pouch" || input["ModelSettings"]["ModelFramework"] == "P4D Cylindrical" + elseif input["ModelSettings"]["ModelFramework"] == "P4D Pouch" || input["ModelSettings"]["ModelFramework"] == "P4D Prismatic" || input["ModelSettings"]["ModelFramework"] == "P4D Cylindrical" output_data = extract_spatial_data(jutul_output[:states]) available_quantities = Dict{String, Any}( @@ -256,7 +256,7 @@ function get_output_states( "PositiveElectrodeActiveMaterialRadius" => r_pe, ) else - error("Unsupported model framework: $(input["ModelSettings"]["ModelFramework"]). Supported frameworks are: P2D, P4D Pouch, P4D Cylindrical.") + error("Unsupported model framework: $(input["ModelSettings"]["ModelFramework"]). Supported frameworks are: P2D, P4D Pouch, P4D Prismatic, P4D Cylindrical.") end for (k, v) in output_data