Skip to main content

STEER OpenCell Design - A Python package for designing and modeling battery cells.

Project description

steer-opencell-design

PyPI version Python License: AGPL v3

A Python package for designing and modeling lithium-ion and sodium-ion battery cells. Part of the STEER platform, steer-opencell-design provides a hierarchical, composable API for building virtual battery cells from raw materials up to complete cell assemblies, with built-in cost, mass, and electrochemical performance calculations.


Table of Contents


Features

  • Hierarchical cell modeling — compose cells from materials → formulations → electrodes → assemblies → complete cells
  • Multiple cell formats — cylindrical, prismatic, pouch, and flex-frame cell architectures
  • Multiple assembly types — wound jelly rolls (round and flat), z-fold stacks, and punched stacks
  • Electrochemical curves — half-cell voltage–capacity curves are combined into full-cell curves with N/P ratio control
  • Cost and mass breakdowns — automatic roll-up of cost and mass from component level to cell level
  • Interactive visualization — Plotly-based cross-sections, top-down views, capacity plots, and sunburst breakdowns
  • Serialization — serialize and deserialize full cell configurations for storage and sharing
  • Database integration — load reference materials and cell designs from the STEER database
  • Change propagation — modify any parameter and automatically recalculate all dependent properties up the hierarchy

Installation

From PyPI

pip install steer-opencell-design

Requires Python >= 3.10. Dependencies (steer-core, steer-materials, numba) are installed automatically.

From Source

git clone https://github.com/stanford-developers/steer-opencell-design.git
cd steer-opencell-design
pip install -e .

Database Connection

from_database() loads reference materials and cell designs. The backend is selected by the OPENCELL_ENV environment variable:

  • development (default) — uses the local SQLite database shipped with the steer-opencell-data package. No network calls; works fully offline. Install it with (requires Git LFS):
pip install git+https://github.com/stanford-developers/steer-opencell-data.git
  • production — uses a deployed OpenCell REST API. Requires the API_URL environment variable:
export OPENCELL_ENV=production
export API_URL=https://api.opencell.example.com/production

No configuration is needed for the default development mode — just install steer-opencell-data and call from_database().


Quickstart

The following example builds a complete cylindrical cell from scratch. The workflow follows the natural hierarchy: Materials → Formulations → Electrodes → Layup → Assembly → Cell.

import steer_opencell_design as ocd

# ── 1. Materials ──────────────────────────────────────────────────

# Load active materials from the built-in database
cathode_active = ocd.CathodeMaterial.from_database("LFP")
cathode_active.specific_cost = 6      # $/kg
cathode_active.density = 3.6          # g/cm³

anode_active = ocd.AnodeMaterial.from_database("Synthetic Graphite")
anode_active.specific_cost = 4
anode_active.density = 2.2

# Create auxiliary materials
conductive_additive = ocd.ConductiveAdditive(
    name="Super P", specific_cost=15, density=2.0, color="#000000"
)
binder = ocd.Binder(name="CMC", specific_cost=10, density=1.5, color="#FFFFFF")

# ── 2. Formulations ──────────────────────────────────────────────

cathode_formulation = ocd.CathodeFormulation(
    active_materials={cathode_active: 95},     # weight %
    binders={binder: 2},
    conductive_additives={conductive_additive: 3},
)

anode_formulation = ocd.AnodeFormulation(
    active_materials={anode_active: 90},
    binders={binder: 5},
    conductive_additives={conductive_additive: 5},
)

# ── 3. Current Collectors ────────────────────────────────────────

cc_material = ocd.CurrentCollectorMaterial(
    name="Aluminum", specific_cost=5, density=2.7, color="#AAAAAA"
)

cathode_cc = ocd.NotchedCurrentCollector(
    material=cc_material,
    length=4500,          # mm
    width=300,            # mm
    thickness=8,          # μm
    tab_width=60,         # mm
    tab_spacing=200,      # mm
    tab_height=18,        # mm
    insulation_width=6,   # mm
    coated_tab_height=2,  # mm
)

anode_cc = ocd.NotchedCurrentCollector(
    material=cc_material,
    length=4500, width=306, thickness=8,
    tab_width=60, tab_spacing=100, tab_height=18,
    insulation_width=6, coated_tab_height=2,
)

# ── 4. Electrodes ────────────────────────────────────────────────

insulation = ocd.InsulationMaterial.from_database("Aluminium Oxide, 99.5%")

cathode = ocd.Cathode(
    formulation=cathode_formulation,
    mass_loading=12,              # mg/cm²
    current_collector=cathode_cc,
    calender_density=2.60,        # g/cm³
    insulation_material=insulation,
    insulation_thickness=10,      # μm
)

anode = ocd.Anode(
    formulation=anode_formulation,
    mass_loading=7.2,
    current_collector=anode_cc,
    calender_density=1.1,
    insulation_material=insulation,
    insulation_thickness=10,
)

# ── 5. Separator & Layup ─────────────────────────────────────────

separator_material = ocd.SeparatorMaterial(
    name="Polyethylene", specific_cost=2, density=0.94,
    color="#FDFDB7", porosity=45,   # %
)

top_separator = ocd.Separator(material=separator_material, thickness=25, width=310, length=5000)
bottom_separator = ocd.Separator(material=separator_material, thickness=25, width=310, length=7000)

layup = ocd.Laminate(
    anode=anode, cathode=cathode,
    top_separator=top_separator, bottom_separator=bottom_separator,
)

# ── 6. Electrode Assembly ────────────────────────────────────────

mandrel = ocd.RoundMandrel(diameter=5, length=350)

tape_material = ocd.TapeMaterial.from_database("Kapton")
tape_material.density = 1.42
tape_material.specific_cost = 70
tape = ocd.Tape(material=tape_material, thickness=30)

jellyroll = ocd.WoundJellyRoll(
    laminate=layup, mandrel=mandrel,
    tape=tape, additional_tape_wraps=5,
)

# ── 7. Encapsulation ─────────────────────────────────────────────

aluminum = ocd.PrismaticContainerMaterial.from_database("Aluminum")
copper = ocd.PrismaticContainerMaterial.from_database("Copper")

encapsulation = ocd.CylindricalEncapsulation(
    cathode_terminal_connector=ocd.CylindricalTerminalConnector(material=aluminum, thickness=2, fill_factor=0.8),
    anode_terminal_connector=ocd.CylindricalTerminalConnector(material=copper, thickness=3, fill_factor=0.7),
    lid_assembly=ocd.CylindricalLidAssembly(material=aluminum, thickness=4.0, fill_factor=0.9),
    canister=ocd.CylindricalCanister(material=aluminum, outer_radius=21.4, height=330, wall_thickness=0.5),
)

# ── 8. Electrolyte & Cell ────────────────────────────────────────

electrolyte = ocd.Electrolyte(
    name="1M LiPF6 in EC:DMC (1:1)",
    density=1.2, specific_cost=15.0, color="#00FF00",
)

cell = ocd.CylindricalCell(
    reference_electrode_assembly=jellyroll,
    encapsulation=encapsulation,
    electrolyte=electrolyte,
    electrolyte_overfill=20,  # %
)

# ── 9. Inspect Results ───────────────────────────────────────────

print(f"Energy:            {cell.energy} Wh")
print(f"Mass:              {cell.mass} g")
print(f"Specific energy:   {cell.specific_energy} Wh/kg")
print(f"Volumetric energy: {cell.volumetric_energy} Wh/L")
print(f"Cost per energy:   {cell.cost_per_energy} $/kWh")

# Visualize
cell.get_cross_section().show()
cell.get_capacity_plot().show()
cell.plot_mass_breakdown().show()
cell.plot_cost_breakdown().show()

More Examples

Prismatic Cell (Stacked)

Build a prismatic cell with punched stacked electrodes
import steer_opencell_design as ocd

# ── Materials ─────────────────────────────────────────────────────

cathode_active = ocd.CathodeMaterial.from_database("LFP")
cathode_active.specific_cost = 6
cathode_active.density = 3.6

anode_active = ocd.AnodeMaterial.from_database("Synthetic Graphite")
anode_active.specific_cost = 4
anode_active.density = 2.2

conductive_additive = ocd.ConductiveAdditive(name="Super P", specific_cost=15, density=2.0, color="#000000")
binder = ocd.Binder(name="CMC", specific_cost=10, density=1.5, color="#FFFFFF")

# ── Formulations ──────────────────────────────────────────────────

cathode_form = ocd.CathodeFormulation(
    active_materials={cathode_active: 95},
    binders={binder: 2},
    conductive_additives={conductive_additive: 3},
)
anode_form = ocd.AnodeFormulation(
    active_materials={anode_active: 90},
    binders={binder: 5},
    conductive_additives={conductive_additive: 5},
)

# ── Current Collectors (Punched for stacking) ────────────────────

al_cc = ocd.CurrentCollectorMaterial(name="Aluminum", specific_cost=5, density=2.7, color="#AAAAAA")
cu_cc = ocd.CurrentCollectorMaterial(name="Copper", specific_cost=10, density=8.96, color="#B87333")

cathode_cc = ocd.PunchedCurrentCollector(
    material=al_cc, length=200, width=100, thickness=12,
    tab_width=40, tab_height=20,
)
anode_cc = ocd.PunchedCurrentCollector(
    material=cu_cc, length=204, width=104, thickness=8,
    tab_width=40, tab_height=20,
)

# ── Electrodes ────────────────────────────────────────────────────

insulation = ocd.InsulationMaterial.from_database("Aluminium Oxide, 99.5%")

cathode = ocd.Cathode(
    formulation=cathode_form, mass_loading=20,
    current_collector=cathode_cc, calender_density=2.60,
    insulation_material=insulation, insulation_thickness=10,
)
anode = ocd.Anode(
    formulation=anode_form, mass_loading=12,
    current_collector=anode_cc, calender_density=1.1,
    insulation_material=insulation, insulation_thickness=10,
)

# ── Separator & Layup ────────────────────────────────────────────

sep_mat = ocd.SeparatorMaterial(name="Polyethylene", specific_cost=2, density=0.94, color="#FDFDB7", porosity=45)
separator = ocd.Separator(material=sep_mat, thickness=20, width=210, length=110)

layup = ocd.MonoLayer(anode=anode, cathode=cathode, separator=separator)

# ── Assembly (Punched Stack) ─────────────────────────────────────

tape_material = ocd.TapeMaterial.from_database("Kapton")
tape_material.density = 1.42
tape_material.specific_cost = 70
tape = ocd.Tape(material=tape_material, thickness=30)

stack = ocd.PunchedStack(mono_layer=layup, n_layers=20, tape=tape)

# ── Encapsulation ─────────────────────────────────────────────────

steel = ocd.PrismaticContainerMaterial.from_database("Steel")
aluminum = ocd.PrismaticContainerMaterial.from_database("Aluminum")
copper = ocd.PrismaticContainerMaterial.from_database("Copper")

encapsulation = ocd.PrismaticEncapsulation(
    cathode_terminal_connector=ocd.PrismaticTerminalConnector(material=aluminum, thickness=2, fill_factor=0.5),
    anode_terminal_connector=ocd.PrismaticTerminalConnector(material=copper, thickness=2, fill_factor=0.5),
    lid_assembly=ocd.PrismaticLidAssembly(material=steel, thickness=2, fill_factor=0.9),
    canister=ocd.PrismaticCanister(
        material=steel, height=120, width=110, depth=220,
        wall_thickness=0.5, base_thickness=0.5,
    ),
)

# ── Electrolyte & Cell ───────────────────────────────────────────

electrolyte = ocd.Electrolyte(name="1M LiPF6 in EC:DMC", density=1.2, specific_cost=15, color="#00FF00")

cell = ocd.PrismaticCell(
    reference_electrode_assembly=stack,
    encapsulation=encapsulation,
    electrolyte=electrolyte,
    n_electrode_assembly=6,
    electrolyte_overfill=20,
    clipped_tab_length=7,
)

print(f"Energy: {cell.energy:.2f} Wh | Mass: {cell.mass:.2f} g | Specific energy: {cell.specific_energy:.2f} Wh/kg")

Pouch Cell (Stacked)

Build a pouch cell with punched stacked electrodes
import steer_opencell_design as ocd

# ── Materials & Formulations (same as prismatic example above) ───
# ... (create cathode_form, anode_form, cathode, anode as before)

# ── Separator & Layup ────────────────────────────────────────────

sep_mat = ocd.SeparatorMaterial(name="Polyethylene", specific_cost=2, density=0.94, color="#FDFDB7", porosity=45)
separator = ocd.Separator(material=sep_mat, thickness=20, width=210, length=110)
layup = ocd.MonoLayer(anode=anode, cathode=cathode, separator=separator)

# ── Assembly ──────────────────────────────────────────────────────

tape_material = ocd.TapeMaterial.from_database("Kapton")
tape_material.density = 1.42
tape_material.specific_cost = 70
tape = ocd.Tape(material=tape_material, thickness=30)

stack = ocd.PunchedStack(mono_layer=layup, n_layers=20, tape=tape)

# ── Pouch Encapsulation ──────────────────────────────────────────

laminate_mat = ocd.LaminateMaterial(name="Al Laminate", specific_cost=5, density=1.5, color="#C0C0C0")
al_mat = ocd.PrismaticContainerMaterial.from_database("Aluminum")
cu_mat = ocd.PrismaticContainerMaterial.from_database("Copper")

encapsulation = ocd.PouchEncapsulation(
    cathode_terminal=ocd.PouchTerminal(material=al_mat, width=40, thickness=200, length=20),
    anode_terminal=ocd.PouchTerminal(material=cu_mat, width=40, thickness=200, length=20),
    top_laminate_sheet=ocd.LaminateSheet(material=laminate_mat, thickness=113, draw_depth=5),
    bottom_laminate_sheet=ocd.LaminateSheet(material=laminate_mat, thickness=113, draw_depth=5),
    seal_width=8,
)

# ── Electrolyte & Cell ───────────────────────────────────────────

electrolyte = ocd.Electrolyte(name="1M LiPF6 in EC:DMC", density=1.2, specific_cost=15, color="#00FF00")

cell = ocd.PouchCell(
    reference_electrode_assembly=stack,
    encapsulation=encapsulation,
    electrolyte=electrolyte,
    n_electrode_assembly=2,
    electrolyte_overfill=20,
    clipped_tab_length=10,
)

print(f"Energy: {cell.energy:.2f} Wh | Mass: {cell.mass:.2f} g")
cell.get_top_down_view().show()

Flex-Frame Cell (Solid-State)

Build a solid-state flex-frame cell with lithium metal anode
import steer_opencell_design as ocd
import pandas as pd

# ── Materials ─────────────────────────────────────────────────────

cathode_active = ocd.CathodeMaterial.from_database("NMC811")
cathode_active.specific_cost = 30
cathode_active.density = 4.87

# Lithium metal anode (custom half-cell curve)
anode_active = ocd.AnodeMaterial(
    name="Lithium Metal",
    half_cell_curve=pd.DataFrame({
        "voltage": [0.0, 0.01, 5.0],
        "capacity": [0.0, 3861, 3862],
    }),
    specific_cost=100,
    density=0.534,
    color="#C0C0C0",
)

conductive_additive = ocd.ConductiveAdditive(name="Super P", specific_cost=15, density=2.0, color="#000000")
binder = ocd.Binder(name="PVDF", specific_cost=10, density=1.78, color="#FFFFFF")

# ── Formulations ──────────────────────────────────────────────────

cathode_form = ocd.CathodeFormulation(
    active_materials={cathode_active: 90},
    binders={binder: 5},
    conductive_additives={conductive_additive: 5},
)
anode_form = ocd.AnodeFormulation(
    active_materials={anode_active: 100},
    binders={},
    conductive_additives={},
)

# ── Current Collectors & Electrodes ──────────────────────────────

al_cc = ocd.CurrentCollectorMaterial(name="Aluminum", specific_cost=5, density=2.7, color="#AAAAAA")
cu_cc = ocd.CurrentCollectorMaterial(name="Copper", specific_cost=10, density=8.96, color="#B87333")
insulation = ocd.InsulationMaterial.from_database("Aluminium Oxide, 99.5%")

cathode = ocd.Cathode(
    formulation=cathode_form, mass_loading=30,
    current_collector=ocd.PunchedCurrentCollector(
        material=al_cc, length=80, width=55, thickness=12, tab_width=25, tab_height=15,
    ),
    calender_density=3.1, insulation_material=insulation, insulation_thickness=10,
)
anode = ocd.Anode(
    formulation=anode_form, mass_loading=2.1,
    current_collector=ocd.PunchedCurrentCollector(
        material=cu_cc, length=82, width=57, thickness=8, tab_width=25, tab_height=15,
    ),
    calender_density=0.534, insulation_material=insulation, insulation_thickness=10,
)

# ── Solid Electrolyte Separator ───────────────────────────────────

sep_mat = ocd.SeparatorMaterial(
    name="LLZO", specific_cost=200, density=5.1,
    color="#E0E0FF", porosity=0,     # solid electrolyte — zero porosity
)
separator = ocd.Separator(material=sep_mat, thickness=30, width=86, length=60)

# ── Layup → Stack ────────────────────────────────────────────────

layup = ocd.MonoLayer(anode=anode, cathode=cathode, separator=separator)

tape_mat = ocd.TapeMaterial.from_database("Kapton")
tape_mat.density = 1.42
tape_mat.specific_cost = 70

stack = ocd.PunchedStack(
    mono_layer=layup, n_layers=1,
    tape=ocd.Tape(material=tape_mat, thickness=30),
)

# ── Flex-Frame Encapsulation ─────────────────────────────────────

frame_mat = ocd.FlexFrameMaterial(name="PEEK", specific_cost=100, density=1.3, color="#D2B48C")
laminate_mat = ocd.LaminateMaterial(name="Al Laminate", specific_cost=5, density=1.5, color="#C0C0C0")
al_mat = ocd.PrismaticContainerMaterial.from_database("Aluminum")
cu_mat = ocd.PrismaticContainerMaterial.from_database("Copper")

encapsulation = ocd.FlexFrameEncapsulation(
    frame=ocd.FlexFrame(material=frame_mat, thickness=200, rim_width=5),
    cathode_terminal=ocd.PouchTerminal(material=al_mat, width=25, thickness=200, length=15),
    anode_terminal=ocd.PouchTerminal(material=cu_mat, width=25, thickness=200, length=15),
    top_laminate_sheet=ocd.LaminateSheet(material=laminate_mat, thickness=113, draw_depth=2),
    bottom_laminate_sheet=ocd.LaminateSheet(material=laminate_mat, thickness=113, draw_depth=2),
)

# ── Catholyte & Cell ─────────────────────────────────────────────

catholyte = ocd.Electrolyte(name="Gel Catholyte", density=1.3, specific_cost=50, color="#90EE90")

cell = ocd.FlexFrameCell(
    reference_electrode_assembly=stack,
    encapsulation=encapsulation,
    catholyte=catholyte,           # FlexFrameCell uses 'catholyte' instead of 'electrolyte'
    clipped_tab_length=8,
)

print(f"Energy: {cell.energy:.2f} Wh | Mass: {cell.mass:.2f} g | Cost: ${cell.cost:.2f}")

Real-World Examples

The steer-opencell-data repository contains a large collection of complete, runnable scripts that build real cell designs with this package — it is the best place to see steer-opencell-design used in practice:

  • cell_references/ — 9 generic reference designs covering cylindrical, prismatic, pouch, and flex-frame formats across multiple chemistries (LFP, NMC, sodium-ion, solid-state)
  • cell_teardowns/ — 10 models of commercial cells reconstructed from published teardown data, complete with measured dimensions, formulations, and materials
  • default_materials/ — scripts that create every material in the database (active materials, current collectors, separators, electrolytes, binders, additives), showing how to define materials from raw property data

These scripts double as the build pipeline for the STEER database: each one constructs cells or materials and serializes them into the local SQLite database that from_database() reads in development mode.


Package Overview

The package is organized into four layers that mirror the physical hierarchy of a battery cell:

Materials  →  Components  →  Constructions  →  Cells

All classes inherit from a rich set of mixins provided by steer-core (validation, serialization, coordinate systems, change propagation, plotting, and database access), giving every object a consistent interface.

Materials (steer_opencell_design.Materials)

Raw materials and electrode formulations.

Class Description
CathodeMaterial / AnodeMaterial Active materials with half-cell voltage–capacity curves
Binder Electrode binder materials (e.g., PVDF, CMC)
ConductiveAdditive Conductive additives (e.g., carbon black, Super P)
CathodeFormulation / AnodeFormulation Blended electrode formulations with weight fractions
Electrolyte Liquid electrolyte materials
SeparatorMaterial Separator base material with porosity
CurrentCollectorMaterial Metal foil material for current collectors
TapeMaterial Adhesive tape material for winding termination
InsulationMaterial Ceramic insulation coatings (e.g., Al₂O₃)
PrismaticContainerMaterial Container housing materials (aluminum, steel)
LaminateMaterial Laminate pouch film materials
FlexFrameMaterial Flex-frame housing materials (e.g., PEEK)

Most materials can be loaded from the built-in database:

material = ocd.CathodeMaterial.from_database("NMC811")
binder = ocd.Binder.from_database("PVDF")

Components (steer_opencell_design.Components)

Physical parts that make up a cell.

Electrodes:

Class Description
Cathode / Anode Complete electrodes with formulation, current collector, and coating parameters

Current Collectors:

Class Description
NotchedCurrentCollector Notched foil for tabless wound cells
TabWeldedCurrentCollector Foil with welded tab strips at specified positions
TablessCurrentCollector Continuous foil with edge-based connections
PunchedCurrentCollector Punched foil with integral tabs for stacked cells

Separators:

Class Description
Separator Porous separator membrane

Containers:

Class Description
CylindricalCanister, CylindricalLidAssembly, CylindricalTerminalConnector, CylindricalEncapsulation Cylindrical can components
PrismaticCanister, PrismaticLidAssembly, PrismaticTerminalConnector, PrismaticEncapsulation Prismatic housing components
PouchEncapsulation, LaminateSheet, PouchTerminal Pouch film components
FlexFrame, FlexFrameEncapsulation Flex-frame housing components

Constructions (steer_opencell_design.Constructions)

Higher-level assemblies that combine components.

Layups — define how electrode layers are arranged:

Class Description
Laminate Two-separator layup for wound cells (top + bottom separator sandwiching cathode and anode)
MonoLayer Single-separator layup for stacked cells
ZFoldMonoLayer Z-fold separator variant of MonoLayer

Electrode Assemblies — define how layups are assembled:

Class Description
WoundJellyRoll Cylindrical (round) wound jelly roll
FlatWoundJellyRoll Flat (racetrack) wound jelly roll for prismatic cells
ZFoldStack Z-fold stacked electrode assembly
PunchedStack Punched/stacked electrode assembly

Cells — complete battery cells:

Class Description
CylindricalCell Cylindrical cell (e.g., 18650, 21700, 4680)
PrismaticCell Prismatic hard-case cell
PouchCell Pouch (soft-pack) cell
FlexFrameCell Flex-frame cell for solid-state designs

Utilities

Class/Function Description
NPRatioControlMode Enum controlling how N/P ratio adjustments propagate
OverhangControlMode Enum controlling electrode overhang behavior
ElectrodeControlMode Enum controlling how electrode properties respond to changes
RoundMandrel / FlatMandrel Winding mandrel geometry for jelly roll assembly
Tape Termination tape for wound assemblies

API Reference

Properties marked settable can be assigned to directly and will trigger recalculation of dependent values. Read-only properties are computed automatically. Every settable property also has a corresponding *_range (soft) and often a *_hard_range (absolute) read-only property that defines the valid bounds for that parameter (omitted from tables below for brevity).

Cell Properties

All cell types (CylindricalCell, PrismaticCell, PouchCell, FlexFrameCell).

Settable:

Property Unit Description
reference_electrode_assembly Reference electrode assembly object
encapsulation Container/encapsulation object
electrolyte Electrolyte object
n_electrode_assembly Number of electrode assemblies in the cell
electrolyte_overfill % Electrolyte overfill percentage
operating_voltage_window (V, V) (min, max) operating voltages
maximum_operating_voltage V Upper voltage limit
minimum_operating_voltage V Lower voltage limit
reversible_capacity Ah Usable discharge capacity (solves for min voltage)
irreversible_capacity Ah Maximum capacity at full charge (solves for max voltage)
name Cell name

Read-only:

Property Unit Description
energy Wh Total cell energy
mass g Total cell mass
cost $ Total cell cost
volume L Cell volume
specific_energy Wh/kg Gravimetric energy density
volumetric_energy Wh/L Volumetric energy density
cost_per_energy $/kWh Normalized cost per energy
capacity_loss Ah Capacity remaining at minimum voltage
capacity_curve DataFrame Full-cell voltage vs. capacity data
cathode_capacity_curve DataFrame Cathode half-cell curve
anode_capacity_curve DataFrame Anode half-cell curve
mass_breakdown dict Nested mass breakdown by component (g)
cost_breakdown dict Nested cost breakdown by component ($)
form_factor str Human-readable form factor (e.g., "Cylindrical")
internal_construction str Assembly type (e.g., "Wound Jelly Roll")
reference_chemistry str Chemistry reference string
electrode_assemblies list All electrode assembly instances in the cell

Electrode Assembly Properties

Shared by all assemblies (WoundJellyRoll, FlatWoundJellyRoll, PunchedStack, ZFoldStack).

Settable (all assemblies):

Property Unit Description
layup Layup object (Laminate, MonoLayer, etc.)
name Assembly name

Read-only (all assemblies):

Property Unit Description
pore_volume cm³ Total pore volume (used for electrolyte fill)
interfacial_area cm² Active interfacial area
capacity_curve DataFrame Full-cell capacity curve (Ah vs. V)
anode_capacity_curve DataFrame Anode half-cell curve
cathode_capacity_curve DataFrame Cathode half-cell curve
cost $ Total assembly cost
cost_breakdown dict Nested cost breakdown
mass g Total assembly mass
mass_breakdown dict Nested mass breakdown

WoundJellyRoll — additional settable:

Property Unit Description
mandrel Mandrel object
tape Tape object
additional_tape_wraps Number of extra tape wraps
tape_length_driver TapeDriver enum (JELLY_ROLL_DRIVEN / TAPE_DRIVEN)
collector_tab_crumple_factor % Tab crumple factor
height mm Overall assembly height
diameter mm Outer diameter (WoundJellyRoll only)
radius mm Outer radius (WoundJellyRoll only)

WoundJellyRoll — additional read-only:

Property Unit Description
roll_properties DataFrame Turn counts per component
spiral DataFrame Spiral coordinates
total_layup_length mm Total unwrapped layup length
total_height mm Total wound height

FlatWoundJellyRoll — additional settable:

Property Unit Description
thickness mm Overall jelly roll thickness
width mm Overall jelly roll width

FlatWoundJellyRoll — additional read-only:

Property Unit Description
pressed_radius mm Pressed mandrel radius
pressed_straight_length mm Pressed mandrel straight length

PunchedStack / ZFoldStack — additional settable:

Property Unit Description
n_layers Number of electrode layers
thickness mm Total stack thickness (adjusts n_layers)

ZFoldStack — additional settable:

Property Unit Description
additional_separator_wraps Extra separator wraps around the stack

Layup Properties

Shared by Laminate, MonoLayer, ZFoldMonoLayer.

Settable:

Property Unit Description
cathode Cathode electrode object
anode Anode electrode object
np_ratio N/P ratio (anode capacity / cathode capacity)
np_ratio_control_mode NPRatioControlMode enum
operating_voltage_window (V, V) (min, max) voltage window
minimum_operating_voltage V Minimum operating voltage
maximum_operating_voltage V Maximum operating voltage
operating_reversible_areal_capacity mAh/cm² Operating reversible areal capacity
electrode_orientation Electrode orientation enum

Read-only:

Property Unit Description
areal_capacity_curve DataFrame Full-cell areal capacity curve (mAh/cm² vs. V)

Electrode Properties

Cathode and Anode.

Settable:

Property Unit Description
formulation Electrode formulation object
current_collector Current collector object
mass_loading mg/cm² Coating mass loading
calender_density g/cm³ Calendered coating density
coating_thickness µm Single-side coating thickness
thickness µm Total electrode thickness (CC + 2× coating)
porosity % Electrode coating porosity
insulation_material Insulation material object
insulation_thickness µm Insulation coating thickness
voltage_cutoff V Half-cell voltage cutoff
control_mode ElectrodeControlMode enum
name Electrode name

Anode-only settable:

Property Unit Description
top_overhang mm Top overhang in layup context
bottom_overhang mm Bottom overhang in layup context

Read-only:

Property Unit Description
mass g Total electrode mass
cost $ Total electrode cost
mass_breakdown dict Nested mass breakdown (g)
cost_breakdown dict Nested cost breakdown ($)
areal_capacity_curve DataFrame Areal capacity vs. voltage
top_side str Which side ('a'/'b') faces up
properties dict Summary dict (cost, mass, thickness)

Formulation Properties

CathodeFormulation and AnodeFormulation.

Settable:

Property Unit Description
active_materials Dict of {ActiveMaterial: weight%}
binders Dict of {Binder: weight%}
conductive_additives Dict of {ConductiveAdditive: weight%}
active_material_1 / _2 / _3 Individual active material objects
active_material_1_weight / _2_weight / _3_weight % Individual weight percentages
binder_1 / _2 Individual binder objects
binder_1_weight / _2_weight % Individual weight percentages
conductive_additive_1 / _2 Individual conductive additive objects
conductive_additive_1_weight / _2_weight % Individual weight percentages
voltage_cutoff V Half-cell voltage cutoff
mass g Formulation mass
volume cm³ Formulation volume
name Formulation name

Read-only:

Property Unit Description
density g/cm³ Blended formulation density
specific_cost $/g Blended specific cost
cost $ Total formulation cost
cost_breakdown dict Nested cost breakdown
mass_breakdown dict Nested mass breakdown
specific_capacity_curve DataFrame Specific capacity curve (mAh/g vs. V)
capacity_curve DataFrame Capacity curve (mAh vs. V)
voltage_operation_window (V, V) Valid voltage range
color str Hex color string

Active Material Properties

CathodeMaterial and AnodeMaterial.

Settable:

Property Unit Description
reversible_specific_capacity mAh/g Reversible specific capacity
irreversible_specific_capacity mAh/g Irreversible specific capacity
reversible_specific_capacity_scaling_percentage % Scaling adjustment for reversible capacity
irreversible_specific_capacity_scaling_percentage % Scaling adjustment for irreversible capacity
voltage_cutoff V Voltage cutoff for half-cell curves
specific_capacity_curves list List of DataFrames (raw curve data)
extrapolation_window V Extrapolation window
reference Reference electrode string (e.g., "Li/Li+")

Read-only:

Property Unit Description
specific_capacity_curve DataFrame Specific capacity curve (mAh/g vs. V)
minimum_extrapolated_voltage V Minimum extrapolated voltage (CathodeMaterial only)

Current Collector Properties

Shared by all current collector types.

Settable (all types):

Property Unit Description
material Current collector material object
thickness µm Foil thickness
insulation_width mm Edge insulation width
name Name string

Tape-style CCs (NotchedCurrentCollector, TablessCurrentCollector) — additional settable:

Property Unit Description
length mm Foil length
width mm Foil width
bare_lengths_a_side (mm, mm) (start, end) bare region on A-side
bare_lengths_b_side (mm, mm) (start, end) bare region on B-side
a_side_coated_section (mm, mm) (start, end) of A-side coating
b_side_coated_section (mm, mm) (start, end) of B-side coating

Tabbed CCs (NotchedCurrentCollector, TabWeldedCurrentCollector, PunchedCurrentCollector) — additional settable:

Property Unit Description
tab_width mm Tab width
tab_height mm Tab height
coated_tab_height mm Coated portion of tab height

Read-only (all types):

Property Unit Description
mass g Total mass
cost $ Total cost
foil_area cm² Single-sided foil area
coated_area cm² Total coated area
a_side_coated_area cm² A-side coated area
b_side_coated_area cm² B-side coated area
insulation_area cm² Total insulation area
top_side str Which side ('a'/'b') faces up
total_height mm Total height including tab (tabbed types)

Separator Properties

Settable:

Property Unit Description
material Separator material object
length mm Separator length
width mm Separator width
thickness µm Separator thickness
areal_cost $/m² Areal cost (adjusts material specific cost)
name Separator name

Read-only:

Property Unit Description
mass g Total separator mass
cost $ Total separator cost
area cm² Separator area
pore_volume mm³ Pore volume (based on porosity)

Mandrel Properties

RoundMandrel and FlatMandrel.

Settable (both):

Property Unit Description
length mm Mandrel length
name Mandrel name

RoundMandrel — settable:

Property Unit Description
radius mm Mandrel radius
diameter mm Mandrel diameter

FlatMandrel — settable:

Property Unit Description
width mm Mandrel width
height mm Mandrel height
radius mm Half of height

FlatMandrel — read-only:

Property Unit Description
straight_length mm Flat segment length

Tape Properties

Settable:

Property Unit Description
material Tape material object
length mm Tape length
width mm Tape width
thickness µm Tape thickness
areal_cost $/m² Areal cost
name Tape name

Read-only:

Property Unit Description
mass g Total tape mass
cost $ Total tape cost
area cm² Total tape area

Visualization Methods

All visualization methods return Plotly go.Figure objects that can be displayed interactively with .show() or exported to HTML/PNG/SVG.

Cell-level:

Method Availability Description
get_cross_section() CylindricalCell Spiral cross-section of the wound jelly roll
get_top_down_view() All cell types Top-down 2D view of the cell
get_side_view() PrismaticCell, PouchCell, FlexFrameCell Side view of the cell
get_capacity_plot() All cell types Full-cell + half-cell voltage–capacity curves
plot_mass_breakdown() All cell types Sunburst chart of mass by component
plot_cost_breakdown() All cell types Sunburst chart of cost by component

Electrode Assembly-level:

Method Availability Description
get_spiral_plot() WoundJellyRoll, FlatWoundJellyRoll Spiral winding path visualization
get_top_down_view() All assemblies Top-down view of the assembly
get_side_view() All assemblies Side view of the assembly
get_capacity_plot() All assemblies Assembly-level capacity curves
plot_mass_breakdown() All assemblies Assembly mass breakdown
plot_cost_breakdown() All assemblies Assembly cost breakdown

Electrode-level:

Method Description
get_cross_section() Electrode cross-section (coating + CC + insulation)
get_top_down_view() Top-down electrode view
get_a_side_view() / get_b_side_view() Individual coating side views
plot_areal_capacity_curve() Areal capacity vs. voltage plot
plot_mass_breakdown() / plot_cost_breakdown() Electrode-level breakdowns

Layup-level:

Method Description
get_top_down_view() Top-down layup view
get_down_top_view() Bottom-up layup view
get_areal_capacity_plot() Layup areal capacity curves

Enums & Control Modes

ElectrodeControlMode

Controls how interdependent electrode properties (mass_loading, calender_density, coating_thickness) respond when one is changed.

Value Behavior
MAINTAIN_CALENDER_DENSITY Default. Keeps calender density constant; adjusts coating thickness.
MAINTAIN_MASS_LOADING Keeps mass loading constant; adjusts coating thickness.
MAINTAIN_COATING_THICKNESS Keeps coating thickness constant; adjusts mass loading or calender density.

NPRatioControlMode

Controls how N/P ratio adjustments propagate through the layup.

Value Behavior
FIXED_ANODE Anode stays fixed; cathode loading adjusts to achieve target N/P ratio.
FIXED_CATHODE Cathode stays fixed; anode loading adjusts.
FIXED_THICKNESS Thickness stays fixed; mass loading and calender density adjust.

OverhangControlMode

Controls how electrode overhang is maintained.

Value Behavior
FIXED_COMPONENT Overhang values shift component positions (dimensions are fixed).
FIXED_OVERHANGS Overhang values change component dimensions (positions stay centered).

Database Materials Catalog

The STEER database includes reference materials that can be loaded with from_database(). Known entries include:

Material Type Available Names
Cathode Active "LFP", "NMC811", "NMC622", "NFM111 (Vendor B)", "NFM111 (Vendor C)", "NFPP", "NaNiMn P2-O3 Composite"
Anode Active "Synthetic Graphite", "Hard Carbon (Vendor A)", "Hard Carbon (Vendor B)"
Binder "PVDF", "CMC", "SBR"
Conductive Additive "Super P", "Graphite", "Carbon Nanotubes"
Insulation "Aluminium Oxide, 99.5%", "Aluminium Oxide, 95%"
Separator "Polyethylene", "Nafion"
Tape "Kapton", "Polyester"
Current Collector "Aluminum", "Copper"
Container "Aluminum", "Copper", "Steel"

Pre-configured reference cells can also be loaded:

cell = ocd.CylindricalCell.from_database(
    table_name="cell_references",
    name="LFP Cylindrical Tabless Cell",
)

Units Convention

Quantity Unit
Length, width, height mm
Thickness (coatings, foils, separators, tapes) μm
Mass loading mg/cm²
Density g/cm³
Specific cost $/kg
Porosity, weight fractions %
Energy Wh
Mass (cell-level) g
Cost (cell-level) $
Specific energy Wh/kg
Volumetric energy density Wh/L
Cost per energy $/kWh

Propagating Changes Through the Hierarchy

steer-opencell-design uses a hierarchical object model where child components are nested inside parent components:

Cell
└── ElectrodeAssembly (JellyRoll, Stack)
    └── Layup (Laminate, MonoLayer)
        ├── Cathode
        │   ├── Formulation
        │   │   └── ActiveMaterials, Binders, etc.
        │   └── CurrentCollector
        ├── Anode
        │   ├── Formulation
        │   └── CurrentCollector
        └── Separators

When you modify a property deep in the hierarchy (e.g., changing the cathode's mass loading), parent objects need to recalculate their derived properties. There are three methods to handle this:

  • propagate_changes() — Recommended. Bubbles recalculation up through all parents automatically.
  • update() — Recalculates a single object without propagating to parents.
  • Re-assignment — Manually re-assign each level for explicit control.

Method 1: propagate_changes() (Recommended)

The simplest approach — modify the property and then call propagate_changes() on that object:

# Modify a property low in the hierarchy
cell.reference_electrode_assembly.layup.cathode.mass_loading = 15

# Propagate changes up to the cell level
cell.reference_electrode_assembly.layup.cathode.propagate_changes()

# Now the cell's energy, mass, cost, etc. are all updated
print(cell.energy)  # Reflects the new mass loading

You can call propagate_changes() from any level in the hierarchy:

# Modify current collector thickness
cell.reference_electrode_assembly.layup.cathode.current_collector.thickness = 12

# Propagate from the current collector level — goes through:
# CurrentCollector → Cathode → Layup → JellyRoll → Cell
cell.reference_electrode_assembly.layup.cathode.current_collector.propagate_changes()

Method 2: update() (Single Level)

If you only need to recalculate a single object without propagating to parents, use update():

# Recalculate just the cathode's properties
cathode.update()

This is useful when making multiple changes before triggering a full recalculation, or when working with standalone components not yet attached to a parent.

Method 3: Re-assignment (Manual Propagation)

You can also trigger recalculation by re-assigning each component through its parent's setter:

# Modify the active material
cell.reference_electrode_assembly.layup.cathode.formulation.active_material_1 = new_material

# Propagate changes up the hierarchy by re-assigning each level
cell.reference_electrode_assembly.layup.cathode.formulation = (
    cell.reference_electrode_assembly.layup.cathode.formulation
)
cell.reference_electrode_assembly.layup.cathode = (
    cell.reference_electrode_assembly.layup.cathode
)
cell.reference_electrode_assembly.layup = (
    cell.reference_electrode_assembly.layup
)
cell.reference_electrode_assembly = (
    cell.reference_electrode_assembly
)

This approach gives you explicit control but is more verbose. The propagate_changes() method is generally preferred.

After Deserialization

When loading a cell from serialized data or a database, parent references may not be established automatically. The propagate_changes() method still works — it simply stops at the first level without a parent.

# Load from database
cell = ocd.CylindricalCell.from_database(table_name="cell_references", name="My Cell")

# Modify and propagate — works correctly
cell.reference_electrode_assembly.layup.cathode.mass_loading = 14
cell.reference_electrode_assembly.layup.cathode.propagate_changes()

Serialization

All cells and components can be serialized and deserialized for storage and sharing:

# Save
data = cell.serialize()

# Restore
restored_cell = ocd.CylindricalCell.deserialize(data)

The serialized output is a plain Python dict that can be converted to JSON for file storage or API transport.


Loading from Database

Reference cells and materials can be loaded from the STEER database. In the default development mode this only requires the steer-opencell-data package to be installed (see Installation).

# Load a pre-configured cell
cell = ocd.CylindricalCell.from_database(
    table_name="cell_references",
    name="LFP Cylindrical Tabless Cell",
)

# Load individual materials
cathode_active = ocd.CathodeMaterial.from_database("NMC811")
separator_mat = ocd.SeparatorMaterial.from_database("Polyethylene")

STEER Ecosystem

steer-opencell-design is part of the STEER platform. The ecosystem is composed of several packages that work together:

Package Role
steer-core Provides the mixin framework shared by all STEER packages — validation, serialization, coordinate systems, change propagation, Plotly-based plotting, and database access.
steer-materials Base material classes with from_database() support, volumetric tracking, and metal subclasses.
steer-opencell-design This package. The cell design API that composes materials and components into complete virtual battery cells with cost, mass, and electrochemical calculations.
steer-opencell-data The open dataset: local SQLite database of materials, reference cell designs, and commercial cell teardowns, plus the scripts that build it. Powers from_database() in development mode.

steer-core and steer-materials are installed automatically when you pip install steer-opencell-design; install steer-opencell-data separately to use the local database (see Database Connection).


Testing

The test suite uses Python's unittest framework via pytest:

# Run all tests
pytest

# Run a specific test file
pytest -k test_cells

# Run with verbose output
pytest -v

# Run a single test class or method
pytest -k "TestCylindricalCell"

Tests run in development mode (OPENCELL_ENV=development, the default), loading reference data from the local SQLite database provided by steer-opencell-data. Install it before running the suite (requires Git LFS):

pip install git+https://github.com/stanford-developers/steer-opencell-data.git

Development

Project Structure

steer-opencell-design/
├── steer_opencell_design/          # Main package
│   ├── Materials/                  # Raw materials & formulations
│   ├── Components/                 # Electrodes, separators, current collectors, containers
│   │   ├── CurrentCollectors/      # Notched, Tabbed, Tabless, Punched
│   │   └── Containers/             # Cylindrical, Prismatic, Pouch, FlexFrame
│   ├── Constructions/              # Assembly-level objects
│   │   ├── Layups/                 # Laminate, MonoLayer, ZFoldMonoLayer
│   │   ├── ElectrodeAssemblies/    # JellyRolls, Stacks, Tape, Mandrels
│   │   └── Cells/                  # CylindricalCell, PrismaticCell, PouchCell, FlexFrameCell
│   └── Utils/                      # Decorators and helper functions
├── test/                           # Unit tests (unittest via pytest)
├── pyproject.toml                  # Build config & dependencies
├── CITATION.cff                    # Citation metadata
└── LICENSE                         # AGPL-3.0 (dual licensed)

Contributing

Contributions are welcome! See CONTRIBUTING.md for guidelines on how to get started.


Citation

If you use this software in your research, please cite it using the metadata in CITATION.cff.


License

OpenCell Design is dual-licensed:

  • Open-source license: AGPL-3.0 — free for open-source projects. If you use OpenCell Design in your software, you must release your software's source code under AGPL-3.0.

  • Commercial license: For proprietary/commercial use without the AGPL copyleft requirement, contact nsiemons@stanford.edu for a commercial license.

See LICENSE for the full AGPL-3.0 license text.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

steer_opencell_design-1.0.42.tar.gz (382.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

steer_opencell_design-1.0.42-py3-none-any.whl (283.8 kB view details)

Uploaded Python 3

File details

Details for the file steer_opencell_design-1.0.42.tar.gz.

File metadata

  • Download URL: steer_opencell_design-1.0.42.tar.gz
  • Upload date:
  • Size: 382.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.0

File hashes

Hashes for steer_opencell_design-1.0.42.tar.gz
Algorithm Hash digest
SHA256 ce9bb5bfc99f0cfd45fd2eea9b057983d8986a541cf0fcfc7987f81db7894841
MD5 ecc426d01ac7d78b979ec97655581f95
BLAKE2b-256 09076429db7b64b657c0d6aa582ccc77e84cdd163f51cfb35280b6d82a0f95b7

See more details on using hashes here.

File details

Details for the file steer_opencell_design-1.0.42-py3-none-any.whl.

File metadata

File hashes

Hashes for steer_opencell_design-1.0.42-py3-none-any.whl
Algorithm Hash digest
SHA256 9340ae66809975ceadbe07aee2f118113b3d61b18dc19df25452b7f494deacb2
MD5 30d5c3d09da2cfb870ce24e6dae81a1d
BLAKE2b-256 a8bc98775e8f9de4748e3b6f2ca0cd94811c05a808d9910937718035681b6966

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page