Skip to content
Merged
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
27 changes: 17 additions & 10 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: CI and Release
name: CI

on:
pull_request:
Expand All @@ -14,29 +14,36 @@ jobs:

steps:
- uses: actions/checkout@v4
- name: Set up Julia
uses: julia-actions/setup-julia@v1
with:
version: "1.11" # choose your Julia version

- name: Set up Python
uses: actions/setup-python@v5
# Set up conda using environment.yml
- name: Set up conda
uses: conda-incubator/setup-miniconda@v3
with:
python-version: "3.x"
activate-environment: test
environment-file: ./environment.yml
auto-activate-base: false

- name: Install dependencies
- name: Install package
shell: bash -l {0}
run: |
python -m pip install --upgrade pip
conda activate test
pip install -e .
pip install pytest pytest-cov

- name: Add GLMakie/XFVB dependencies
run: sudo apt-get update && sudo apt-get install -y xorg-dev mesa-utils xvfb libgl1 freeglut3-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libxext-dev libcairo2-dev libfreetype6-dev libffi-dev libjpeg-dev libpng-dev libz-dev
- name: Run tests with coverage
shell: bash -l {0}
run: |
conda activate test
pytest --cov=battmo --cov-report=term-missing

release:
name: Build and publish to PyPI
runs-on: ubuntu-latest
needs: test
if: startsWith(github.ref, 'refs/tags/v') # only run for version tags
if: startsWith(github.ref, 'refs/tags/pybattmo-v') # only run for pybattmo version tags

steps:
- uses: actions/checkout@v4
Expand Down
83 changes: 82 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,83 @@
# PyBattMo
A Python wrapper around BattMo.jl

**PyBattMo** is a Python wrapper around the Julia-based [BattMo.jl](https://github.com/BattMoTeam/BattMo.jl).

## Installation

In addition to Python, Julia needs to be installed. Visit the [Julia website](https://julialang.org/install/) for more information on how to install Julia. To install PyBattMo, use the following pip command:

```bash
pip install battmo
```

## Quick start

Run examples using our internal library, for example the cell parameters from [Chen et al.](https://doi.org/10.1149/1945-7111/ab9050)

### P2D example

Here are a few examples to get you started:

```python
from battmo import *
import plotly.express as px
import pandas as pd
import numpy as np

# Initialize the model
model = LithiumIonBattery()

# Load cell parameters and cycling protocol
cell_parameters = load_cell_parameters(from_default_set = "Chen2020")
cycling_protocol = load_cycling_protocol(from_default_set = "CCDischarge")

# Set up the simulation
sim = Simulation(model, cell_parameters, cycling_protocol)

# Run the simulation
output = solve(sim)

# Have a quick look into what kind of cell we're dealing with
print_cell_info(cell_parameters)

# Setup model and simulation
model = LithiumIonBattery()
sim = Simulation(model, cell_parameters, cycling_protocol)
output = solve(sim)

# Have a look into which output quantities are available
print_output_overview(output)

# Plotting using Plotly
df = to_pandas(output.time_series)
fig = px.line(df, x="Time", y="Voltage", title="Voltage curve")
fig.show()

# Use BattMo internal plotting functions
install_plotting()
plot_dashboard(output, plot_type="contour")

```

### Run a 3D simulation

```python
from battmo import *

# Load parameter sets and settings
cell_parameters = load_cell_parameters(from_default_set="Chen2020")
cycling_protocol = load_cycling_protocol(from_default_set="CCDischarge")
model_settings = load_model_settings(from_default_set="P4D_cylindrical")

# Setup model and simulation
model = LithiumIonBattery(model_settings=model_settings)
sim = Simulation(model, cell_parameters, cycling_protocol, simulation_settings=simulation_settings)
output = solve(sim)

# Plot interative 3D results
plot_interactive_3d(output)
```

## Documentation

Some additional examples are be found in the [BattMo.jl documentation](https://battmoteam.github.io/BattMo.jl/dev/pybattmo/installation) as well as a comprehensive documentation on the API and architecture of BattMo.jl.
14 changes: 7 additions & 7 deletions battmo/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from .julia_import import jl
from .input_handling import *
from .output_handling import *
from .models import *
from .solving_problems import *
from .utils import *
from .plotting import *
from .julia_import import jl, update_battmo
from .api.input import *
from .api.output import *
from .api.models import *
from .api.solve import *
from .api.tools import *
from .api.plotting import *
6 changes: 6 additions & 0 deletions battmo/api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from .input import *
from .output import *
from .models import *
from .solve import *
from .tools import *
from .plotting import *
25 changes: 24 additions & 1 deletion battmo/input_handling.py → battmo/api/input.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from .julia_import import jl
from ..julia_import import jl
from juliacall import Main as jl_main


def load_cell_parameters(*arg, **kwargs):
Expand All @@ -17,6 +18,14 @@ def load_simulation_settings(*arg, **kwargs):
return jl.load_simulation_settings(*arg, **kwargs)


def load_solver_settings(*arg, **kwargs):
return jl.load_solver_settings(*arg, **kwargs)


def load_full_simulation_input(*arg, **kwargs):
return jl.load_full_simulation_input(*arg, **kwargs)


def CellParameters(*arg, **kwargs):
return jl.CellParameters(*arg, **kwargs)

Expand All @@ -31,3 +40,17 @@ def ModelSettings(*arg, **kwargs):

def SimulationSettings(*arg, **kwargs):
return jl.SimulationSettings(*arg, **kwargs)


def expose_to_battmo(func):
name = func.__name__
setattr(jl_main, name, func) # register Python function in Main

jl.eval(
f"""
function {name}_jl(*args)
return Float64(Main.{name}(*args))
end
Main.{name} = {name}_jl
"""
)
9 changes: 9 additions & 0 deletions battmo/api/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from ..julia_import import jl


def LithiumIonBattery(*arg, **kwargs):
return jl.LithiumIonBattery(*arg, **kwargs)


def SodiumIonBattery(*arg, **kwargs):
return jl.SodiumIonBattery(*arg, **kwargs)
13 changes: 13 additions & 0 deletions battmo/api/output.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from ..julia_import import jl
import numpy as np
import pandas as pd


def to_pandas(julia_data):
# Get field names as Python strings
fields = [str(f) for f in jl.keys(julia_data)]
# Build dictionary with NumPy arrays
data = {f: np.array(julia_data[f]) for f in fields}
# Create DataFrame
df = pd.DataFrame(data)
return df
74 changes: 74 additions & 0 deletions battmo/api/plotting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import juliapkg
from ..julia_import import jl
import juliacall


def activate_plotting():
try:
jl.seval("using WGLMakie; WGLMakie.activate!()")
except:
print("Unable to load WGLMakie. Have you called install_plotting()?")

return False
return True


def install_plotting():
juliapkg.add("WGLMakie", "276b4fcb-3e11-5398-bf8b-a0c2d153d008")
juliapkg.resolve()
activate_plotting()
return True


def uninstall_plotting():
juliapkg.rm("WGLMakie", "276b4fcb-3e11-5398-bf8b-a0c2d153d008")
juliapkg.resolve()
return True


def make_interactive():
juliacall.interactive()


def plot_dashboard(output, plot_type="simple"):
if activate_plotting():
fig = jl.plot_dashboard(output, plot_type=plot_type)
make_interactive()
if plot_type == "line":
jl.seval(
"""
display(current_figure())
println("Press Ctrl+C to stop plotting interactivity")
while true
sleep(0.1)
end
"""
)
else:
jl.seval("display(current_figure())")

return fig


def plot_output(*arg, **kwargs):
if activate_plotting():
fig = jl.plot_output(*arg, **kwargs)
make_interactive()
jl.seval("display(current_figure())")
return fig


def plot_interactive_3d(*arg, **kwargs):
if activate_plotting():
fig = jl.plot_interactive_3d(*arg, **kwargs)
make_interactive()
jl.seval(
"""
display(current_figure())
println("Press Ctrl+C to stop plotting interactivity")
while true
sleep(0.1)
end
"""
)
return fig
27 changes: 27 additions & 0 deletions battmo/api/solve.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from ..julia_import import jl


def Simulation(*arg, **kwargs):
return jl.Simulation(*arg, **kwargs)


def VoltageCalibration(*arg, **kwargs):
return jl.VoltageCalibration(*arg, **kwargs)


def solve(*arg, **kwargs):
return jl.solve(*arg, **kwargs)


def free_calibration_parameter(cal, parameter_path, **kwargs):
parameter_path_jl = jl.seval(f"[{','.join([f'\"{p}\"' for p in parameter_path])}]")
julia_func = getattr(jl, "free_calibration_parameter!")
return julia_func(cal, parameter_path_jl, **kwargs)


def print_calibration_overview(*arg, **kwargs):
return jl.print_calibration_overview(*arg, **kwargs)


def run_simulation(*arg, **kwargs):
return jl.run_simulation(*arg, **kwargs)
14 changes: 13 additions & 1 deletion battmo/utils.py → battmo/api/tools.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .julia_import import jl
from ..julia_import import jl


def print_submodels_info(*arg, **kwargs):
Expand All @@ -21,9 +21,21 @@ def print_output_variable_info(*arg, **kwargs):
return jl.print_output_variable_info(*arg, **kwargs)


def print_output_overview(*arg, **kwargs):
return jl.print_output_overview(*arg, **kwargs)


def generate_default_parameter_files(*arg, **kwargs):
return jl.generate_default_parameter_files(*arg, **kwargs)


def write_to_json_file(*arg, **kwargs):
return jl.write_to_json_file(*arg, **kwargs)


def print_cell_info(*arg, **kwargs):
return jl.print_cell_info(*arg, **kwargs)


def plot_cell_curves(*arg, **kwargs):
return jl.plot_cell_curves(*arg, **kwargs)
34 changes: 29 additions & 5 deletions battmo/julia_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,37 @@
import numpy as np

# Load the main packages

try:
jl.seval("using BattMo")
except Exception:
jl.seval(
"""
import Pkg
Pkg.add(url="https://github.com/BattMoTeam/BattMo.jl")
using BattMo
using BattMo,
using Jutul:Jutul,get_1d_interpolator
using WGLMakie
"""
)

except Exception as e:
jl.seval(
"""
import Pkg
Pkg.add("BattMo")
Pkg.add("Jutul")
Pkg.add("WGLMakie")
Pkg.instantiate()

using BattMo
using Jutul:Jutul,get_1d_interpolator
"""
)


def update_battmo():

jl.seval(
"""
import Pkg
Pkg.add("BattMo") # ensures latest version
"""
)
print("BattMo updated.")
Loading
Loading