Skip to content
Merged
32 changes: 32 additions & 0 deletions docs/api_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,38 @@ or `RateMapBattery` directly.
`RateMapBattery.from_json(path, series=..., parallel=...)` loads one cell
dataset and applies the requested pack topology at runtime.

Main `RateMapBattery` point-state helpers:

| Method | Description |
|---|---|
| `state_at_current(state, current_a)` | Evaluate voltage, power, C-rate, efficiency, and feasibility at pack current |
| `state_at_c_rate(state, c_rate)` | Evaluate the state at cell C-rate |
| `state_at_voltage(state, voltage_v)` | Evaluate current required to hold pack terminal voltage |
| `state_at_power(state, power_w)` | Evaluate current and voltage at pack terminal power |
| `state_at_load_resistance(state, resistance_ohm)` | Evaluate a resistive load |
| `state_at_power_loss(state, power_loss_w)` | Evaluate a requested pack internal loss power |

Main `RateMapBattery` integration helpers:

| Method | Description |
|---|---|
| `integrate_current(...)` | Integrate over time at constant pack current |
| `integrate_c_rate(...)` | Integrate over time at constant cell C-rate |
| `integrate_power(...)` | Integrate over time at constant pack terminal power |
| `integrate_voltage(...)` | Integrate over time at constant pack terminal voltage |
| `integrate_load_resistance(...)` | Integrate over time at constant pack load resistance |
| `integrate_power_loss(...)` | Integrate over time at constant pack internal loss power |
| `integrate_current_to_dod(...)` | Integrate constant current until target DOD |
| `integrate_c_rate_to_dod(...)` | Integrate constant C-rate until target DOD |
| `integrate_power_to_dod(...)` | Integrate constant power until target DOD |
| `integrate_voltage_to_dod(...)` | Integrate constant voltage until target DOD |
| `integrate_load_resistance_to_dod(...)` | Integrate constant load resistance until target DOD |
| `integrate_power_loss_to_dod(...)` | Integrate constant internal loss power until target DOD |
| `integrate_power_profile(...)` | Integrate consecutive constant-power mission segments |

`BatteryIntegrationResult` reports final state, sampled histories, delivered
energy, consumed charge, feasibility, and stop reason.

## System and Propeller Specs

| Class | Purpose |
Expand Down
91 changes: 80 additions & 11 deletions docs/battery_model.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# Battery Model

Status: implemented model note for issue #3.

PyThrust supports a fixed-voltage battery model and a lightweight rate-map
battery model. The fixed-voltage path is useful for quick propulsion sizing,
but it hides two effects that matter for electric aircraft performance studies:
Expand Down Expand Up @@ -104,6 +102,7 @@ The most important point-state functions are:
| Specified voltage | `cellStateV` | $I = (OCV - V) / R$ |
| Specified power | `cellStateP` | solve $P = I(OCV - RI)$ |
| Specified load resistance | `cellStateR` | $I = OCV / (R + R_{load})$ |
| Specified internal loss | `cellStatePloss` | solve $P_{loss} = I^2R$ |

For specified power, the current is obtained from the quadratic:

Expand All @@ -128,6 +127,73 @@ The implementation reports infeasible states when requested power exceeds this
limit, when current exceeds the configured limit, or when terminal voltage falls
below cutoff.

## Integration Modes

`RateMapBattery` can integrate battery state through time or to a target depth
of discharge. All integration methods return `BatteryIntegrationResult`, which
contains the final state, sampled histories, delivered energy, consumed charge,
feasibility, and stop reason.

| PyThrust method | bat-perf analogue | Load held constant |
|---|---|---|
| `integrate_current(...)` | `cellIntIt` | Pack current |
| `integrate_c_rate(...)` | `cellIntCt` | Cell C-rate |
| `integrate_power(...)` | `cellIntPt` | Pack terminal power |
| `integrate_voltage(...)` | `cellIntVt` | Pack terminal voltage |
| `integrate_load_resistance(...)` | `cellIntRt` | Pack load resistance |
| `integrate_power_loss(...)` | `cellIntPlosst` | Pack internal loss power |

The target-DOD variants stop at a requested final DOD instead of a requested
duration:

| PyThrust method | bat-perf analogue |
|---|---|
| `integrate_current_to_dod(...)` | `cellIntIdod` |
| `integrate_c_rate_to_dod(...)` | `cellIntCdod` |
| `integrate_power_to_dod(...)` | `cellIntPdod` |
| `integrate_voltage_to_dod(...)` | `cellIntVdod` |
| `integrate_load_resistance_to_dod(...)` | `cellIntRdod` |
| `integrate_power_loss_to_dod(...)` | `cellIntPlossdod` |

Additional helpers cover inverse and segmented calculations:

| Method | Purpose |
|---|---|
| `dod_at_voltage_power(...)` | Find the DOD where a requested voltage and power coincide |
| `dod_at_power_voltage(...)` | Equivalent solve from the constant-power state equation |
| `integrate_power_profile(...)` | Integrate consecutive constant-power mission segments |

!!! note "Numerical method"
Time and target-DOD integrations use SciPy's adaptive `solve_ivp`
integrator with `max_step_s` as the maximum time step for time-domain
solves. Stop events detect current limits, voltage limits, cutoff voltage,
and DOD exhaustion. This is closer to the `ode45` workflow used by
`bat-perf` than fixed-step Coulomb counting.

Example:

```python
from pythrust.battery import BatteryState, RateMapBattery

battery = RateMapBattery.from_json(
"data/batteries/example_liion_cell.json",
series=4,
parallel=2,
)
state = BatteryState(soc=1.0)

result = battery.integrate_power(
state=state,
power_w=180.0,
dt_s=300.0,
max_step_s=1.0,
)

print(result.delivered_energy_wh)
print(result.final_state.dod)
print(result.stop_reason)
```

## Python API

Use explicit names for the two battery fidelities:
Expand Down Expand Up @@ -173,10 +239,11 @@ voltage = battery.terminal_voltage(current_a=current, state=state)
power = battery.terminal_power(current_a=current, state=state)
```

`RateMapBattery` also supports state advancement:
`RateMapBattery` also supports state advancement and endurance integration:

```python
next_state = battery.step_power(power_w=power, dt_s=dt, state=state)
result = battery.integrate_power(state=state, power_w=power, dt_s=dt)
```

For the fixed-voltage model, `terminal_voltage` returns the configured voltage.
Expand Down Expand Up @@ -209,11 +276,9 @@ dataset:
battery = RateMapBattery.from_json(cell_path, series=4, parallel=2)
```

The current implementation interpolates `OCV(dod)` and `R(dod)` directly. A
later calibration utility can derive these curves from manufacturer C-rate
discharge maps. Manufacturer discharge curves are usually terminal voltage
under load, so real datasets should document how `OCV(dod)` and `R(dod)` were
derived.
The current implementation interpolates `OCV(dod)` and `R(dod)` directly.
Manufacturer discharge curves are usually terminal voltage under load, so real
datasets should document how `OCV(dod)` and `R(dod)` were derived.

## Solver Integration

Expand Down Expand Up @@ -242,23 +307,27 @@ $$
This keeps the propeller/motor equilibrium as a one-dimensional root solve
because current remains a function of RPM through the propeller torque demand.

For mission simulation, evaluate each time step with the current state, compute
For mission simulation, evaluate each segment with the current state, compute
pack current/power, then advance DOD:

$$
x_{next} = x + \frac{I_{cell}}{Q_{cell}} \Delta t
$$

## Implementation Status
## Feature Summary

The initial implementation includes:
The battery package provides:

- `pythrust.battery.FixedVoltageBattery`
- `pythrust.battery.RateMapBattery`
- `pythrust.battery.BatteryState` and `BatteryPoint`
- JSON cell datasets with explicit series and parallel counts at load time
- Solver integration through `solve_operating_point(..., battery_state=...)`
- `OperatingPoint` battery outputs for voltage, current, C-rate, and efficiency
- SciPy-based integration for current, C-rate, power, voltage, resistance, and
internal power-loss modes
- Target-DOD integration, energy knockdown helpers, and power-profile
integration
- A runnable rate-map mission example

## References
Expand Down
7 changes: 4 additions & 3 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ It demonstrates:
| `state_at_voltage` | Current required to hold a requested pack voltage |
| `state_at_power` | Current and voltage for a requested pack power |
| `state_at_load_resistance` | Battery behavior under a resistive load |
| `state_at_power_loss` | Battery behavior for a requested internal loss power |
| Infeasible power | How the model reports a power limit |

This example is intentionally independent from the propulsion solver. It
Expand All @@ -97,8 +98,8 @@ PYTHONPATH=. python examples/rate_map_battery_mission.py

This example couples `RateMapBattery` to `PropulsionSolver` over a short
segment schedule. Each segment solves the propulsion operating point using the
current battery state, reports pack current and voltage, then advances state of
charge from the solved battery current.
current battery state, reports pack current and voltage, then integrates state
of charge from the solved battery current.

It demonstrates:

Expand All @@ -107,7 +108,7 @@ It demonstrates:
| Load cell data | Use `data/batteries/example_liion_cell.json` with explicit series and parallel counts |
| Solve segment | Pass `battery_state` into `solve_operating_point(...)` |
| Read outputs | Inspect `battery_voltage_v` and `battery_current_a` on `OperatingPoint` |
| Advance state | Use `step_current(...)` to update SoC for the next segment |
| Advance state | Use `integrate_current(...)` to update SoC and delivered energy for the next segment |

![Rate-map battery mission simulation](images/rate_map_battery_mission.png)

Expand Down
7 changes: 4 additions & 3 deletions examples/rate_map_battery_mission.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Simulate a simple mission with a rate-map battery.

This example couples RateMapBattery to PropulsionSolver. Each segment solves
the propulsion operating point from the current battery state, then advances
the propulsion operating point from the current battery state, then integrates
state of charge using the solved battery current.

Usage::
Expand Down Expand Up @@ -83,13 +83,14 @@ def main():
reason = op.infeasible_reason or "unknown"
raise SystemExit(f"Mission segment '{segment['name']}' is infeasible: {reason}")

next_state = battery.step_current(
battery_result = battery.integrate_current(
state=state,
current_a=op.battery_current_a,
dt_s=segment["duration_s"],
)
next_state = battery_result.final_state

total_energy_wh += op.battery_power_w * segment["duration_s"] / 3600.0
total_energy_wh += battery_result.delivered_energy_wh
total_time_s += segment["duration_s"]

Comment thread
karakayahuseyin marked this conversation as resolved.
print(
Expand Down
5 changes: 3 additions & 2 deletions examples/rate_map_battery_point_states.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""Evaluate rate-map battery point states.

This example loads a cell-level rate-map dataset, applies a 4S2P pack topology,
and evaluates the same battery state under current, C-rate, voltage, power, and
load-resistance requests.
and evaluates the same battery state under current, C-rate, voltage, power,
load-resistance, and internal-loss requests.

Usage::

Expand Down Expand Up @@ -51,6 +51,7 @@ def main():
print_point("voltage 14 V", battery.state_at_voltage(state=state, voltage_v=14.0))
print_point("power 180 W", battery.state_at_power(state=state, power_w=180.0))
print_point("load 1.5 ohm", battery.state_at_load_resistance(state=state, resistance_ohm=1.5))
print_point("loss 8 W", battery.state_at_power_loss(state=state, power_loss_w=8.0))
print_point("too much power", battery.state_at_power(state=state, power_w=3000.0))

next_state = battery.step_current(state=state, current_a=12.0, dt_s=60.0)
Expand Down
3 changes: 2 additions & 1 deletion pythrust/battery/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

from .fixed import FixedVoltageBattery
from .rate_map import RateMapBattery
from .state import BatteryPoint, BatteryState
from .state import BatteryIntegrationResult, BatteryPoint, BatteryState

__all__ = [
"BatteryIntegrationResult",
"BatteryPoint",
"BatteryState",
"FixedVoltageBattery",
Expand Down
Loading
Loading