Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.ionworks.com/llms.txt

Use this file to discover all available pages before exploring further.

The Universal Cycler Protocol (UCP) is a flexible YAML-based language for defining battery cycling experiments. It allows you to create complex experimental sequences with programmatic control.
You can create and edit UCP protocols visually using the Protocol Builder, or by converting a commercial protocol into UCP format. This page is the full reference for the underlying UCP file format.

Global Configuration

You can define global settings for the entire protocol under the optional global key.
global:
  initial_temperature: 25
  initial_state_type: soc_percentage # or "voltage"
  initial_state_value: 100 # percent if soc_percentage, volts if voltage
  resolution:
    time: 60
  • initial_temperature: The starting temperature for the experiment in degrees Celsius. Defaults to 25. When used in a design optimization, this value is automatically converted to Kelvin and applied as both the initial and ambient temperature for the simulation.
  • initial_state_type: How to define the initial state of the cell. Allowed values:
    • soc_percentage: Interpret initial_state_value as a percentage of state of charge (0–100).
    • voltage: Interpret initial_state_value as a starting voltage in Volts.
  • initial_state_value: The initial value for the selected type.
  • resolution: The time resolution for the simulation output in seconds. Defaults to 60.

Safety Limits

You can define optional safety limits for the simulation. If any of these are breached, the simulation jumps to a recovery step (goto) or, if no goto is supplied, ends the test. Each limit may be given as a bare number or as an object with an explicit goto so that different fault conditions can route to different recovery steps:
safety_limits:
  # Object form — this limit has its own dedicated recovery step.
  voltage_max:
    value: 4.525
    goto: Pause_Voltage_Fault
    delay: 3 # Optional debounce in seconds. The safety only trips when
             # ``Voltage > 4.525 V`` AND the step has been running for more
             # than 3 s, mirroring the Maccor ``VOLT>=4.525&STIME>3`` idiom.
  # Bare-number form — falls back to the protocol-level `goto` below.
  voltage_min: 2.5
  temperature_min: -20
  temperature_max:
    value: 60
    goto: Pause_Thermal_Fault
  charge_current_max: 10 # Max charge current in Amps (positive value)
  discharge_current_max: 15 # Max discharge current in Amps (positive value)
  # Optional fallback target for any limit that doesn't specify its own `goto`.
  goto: Pause_Generic_Fault
Resolution order when a limit triggers:
  1. If the triggering limit has its own goto, jump there.
  2. Otherwise, if safety_limits.goto is set, jump there.
  3. Otherwise, the test ends.
If a step’s termination condition (see below) has the same value as a global safety_limit, the safety limit takes precedence.

Simulation Steps

Each simulation step is defined as a dictionary with a single key that defines the step’s direction. The value of this key is a dictionary containing the step’s parameters. The primary step directions are:
  • Rest: The cell is at rest.
  • Charge: The cell is being charged.
  • Discharge: The cell is being discharged.
  • Drive: A drive cycle step (see below).
  • EIS: An Electrochemical Impedance Spectroscopy step (see below).

Step Parameters

  • mode: The control mode for the step (e.g., C-rate, Current, Power, Voltage). Required unless the step is Rest.
  • value: The setpoint for the control mode. This can be a fixed number or a dynamic expression.
  • duration: The maximum duration of the step in seconds.
  • ends: A list of one or more cut-off conditions. A step must have either a duration or at least one ends condition.
  • temperature: (Optional) The ambient temperature for this specific step in Celsius, overriding the global setting.
  • resolution: (Optional) The time resolution for this specific step, overriding the global setting.
  • set_variable: (Optional) A list of variables to calculate and set after the step completes.
  • note: (Optional) A string for informational purposes.

Parameterized Steps with input['...']

You can use input['...'] syntax in value, duration, and ends fields to create parameterized protocols. When running the protocol, the actual values are provided at runtime.
steps:
  # Parameterized step value
  - Discharge:
      mode: C-rate
      value: input["C-rate"]
      ends:
        - Voltage < input["Cut-off voltage [V]"]

  # Parameterized duration
  - Rest:
      duration: input["Rest duration [s]"]

  # Parameterized duration in ends
  - Discharge:
      mode: Power
      value: 2
      ends:
        - "Duration > input['Cruise duration [s]']"
        - Voltage < 3.0

  # Expressions with inputs
  - Rest:
      duration: input["Duration [s]"] / 2
Inputs can also be used in resolution, global settings, and set_variable expressions. See Experiment Templates for complete examples.

Termination Conditions (ends)

The ends conditions define when a step should terminate.
# Simple termination
ends:
  - "Voltage > 4.2"

# Derivative-based termination
ends:
  - "d/dt(Voltage) < 0.001"

# Termination with a `goto` to another step block
ends:
  - "Voltage < 2.7":
      goto: My_Next_Step
  • Supported Types: Voltage, Current, C-rate, Capacity, Temperature (case-insensitive).
  • Supported Operators: < and >.
  • Sign Convention: For Current, C-rate, and Capacity terminations, always provide a positive value. The engine automatically handles the internal sign convention (e.g., negative current for charge). For Voltage and Temperature, signs are respected as written.
  • Derivative Terminations: To terminate based on the rate of change of a variable, use the format "d/dt(Type) operator value".
If a step’s termination condition is met at the very beginning (e.g., trying to charge a battery that is already at 4.2V), the step will be skipped. Any goto transitions on that termination condition will not be executed.

Example: Derivative Termination

Derivative-based terminations are useful for ending a step based on the stability of a signal. For example, a constant-voltage charge phase can be terminated when the current stops changing, which indicates the battery is full.
- CV Charge with Current Stability Check:
    - Charge:
        mode: Voltage
        value: 4.2 # Constant Voltage phase
        ends:
          - "d/dt(Current) < 0.001"

Example: CCCV Charge

- CC Charge:
    - Charge:
        mode: C-rate
        value: 1
        ends:
          - "Voltage > 4.2"
- CV Charge:
    - Charge:
        mode: Voltage
        value: 4.2
        ends:
          - "Current < 0.05"

EIS Steps

The EIS step performs an Electrochemical Impedance Spectroscopy measurement at the current state of the cell.
  • lower_frequency: The lower frequency bound for the EIS sweep in Hz.
  • upper_frequency: The upper frequency bound for the EIS sweep in Hz.
Both frequency parameters can be a fixed number or a dynamic expression.

Example: Basic EIS

Perform an EIS measurement after a 30-minute rest period.
- Rest:
    - Rest:
        duration: 1800
- Impedance Measurement:
    - EIS:
        lower_frequency: 0.1
        upper_frequency: 1000

Example: EIS with Dynamic Frequencies

The frequencies can be calculated dynamically using variables.
- Set Up Frequencies:
    - Control:
        set_variable:
          - name: VAR_UPPER_FREQ
            eval: 10 * input['Frequency Multiplier']
- Perform EIS:
    - EIS:
        lower_frequency: VAR_UPPER_FREQ / 1000
        upper_frequency: VAR_UPPER_FREQ

Control Steps

Control steps are used for programmatic logic and do not run a simulation. They are ideal for setting variables or creating loops with goto.
- Setup and Jump:
    - Control:
        set_variable:
          - name: VAR_C_RATE
            eval: 1.5
    - "Increment cycle number"

Control step parameters

  • set_variable: (Optional) A list of variables to set (see dynamic behavior with variables).
  • goto: (Optional) The name of the step block to jump to after executing this control step. This enables non-sequential control flow, such as branching to different steps based on variable values.
- Assign C-rate and Continue:
    - Control:
        set_variable:
          - name: VAR_C_RATE
            eval: 0.33
        goto: Start_Discharge
Special string commands are also available:
  • "Increment cycle number": Increments the internal cycle counter.
  • "End" or "Pause": Terminates the entire simulation.

Dynamic Behavior with Variables

You can define variables using set_variable and use them to create dynamic, responsive protocols.
  • name: The name of the variable. Must start with VAR_.
  • eval: A Python expression to be evaluated. The result must be a number.
The eval expression can use:
  • User Inputs: input['...']
  • Other Variables: Any VAR_ variable already defined.
  • Special Variables: t (step-relative time in seconds), Cycle (0-indexed cycle counter).
  • Simulation Results: Voltage, Current, etc. from the previous step.
  • Helper Functions: first(), last(), mean(), abs(), ifelse(), etc.

Initialising variables

Expressions are evaluated eagerly — ifelse(condition, A, B) evaluates both branches before picking one — so a variable referenced anywhere in an expression must already exist. Use a Control step at the top of the protocol to seed every variable you’ll write to:
- Initialize Variables:
    - Control:
        set_variable:
          - { name: VAR_REFERENCE_CAPACITY, eval: "0" }
          - { name: VAR_PEAK_TEMP, eval: "25" }
        note: Seed accumulator variables before the cycling loop.
This matters most for the common “set on first cycle, carry over otherwise” idiom:
set_variable:
  - name: VAR_REFERENCE_CAPACITY
    # Without an init Control step above, VAR_REFERENCE_CAPACITY is undefined
    # the first time this expression runs and the simulation will fail.
    eval: ifelse(Cycle == 0, last(Capacity), VAR_REFERENCE_CAPACITY)
Protocols imported from a Maccor .000 file automatically gain a leading Initialize Variables Control block that seeds every referenced VARn to 0 — this mirrors the way real Maccor cyclers default user variables in hardware. You’ll see it at the top of the parsed UCP YAML.

Example: Time-Varying Control

Linearly ramp the C-rate from 0.1 to 1.1 over one hour.
- Ramp Discharge:
    - Discharge:
        mode: C-rate
        value: 0.1 + t / 3600
        duration: 3600

Example: Conditional Logic

Use the ifelse helper to create conditional logic. This example defines a subsequent step’s direction based on the final voltage of the previous step.
- Discharge and Check:
    - Discharge:
        mode: C-rate
        value: 1
        duration: 600
        set_variable:
          - name: VAR_NEEDS_CHARGE
            eval: ifelse(last(Voltage) < 3.5, 1, 0)

- Conditional Step:
    - Direction[ifelse(VAR_NEEDS_CHARGE == 1, "Charge", "Rest")]:
        mode: C-rate
        value: 1
        ends:
          - "Voltage > input['Upper voltage cut-off [V]']"

Step Blocks

Steps can be grouped into named blocks. This is essential for goto targets and for repeating a sequence of steps using the repeat keyword.
- Ten Pulses:
    - Discharge:
        mode: C-rate
        value: 5
        duration: 1
    - Rest:
        duration: 1
  repeat: 10
A block’s name cannot be one of the reserved step types (Charge, Discharge, Rest, Control, etc.).

Drive Cycles

You can use a drive cycle for complex profiles by passing data to the solve_protocol function in your Python code and referencing it in the YAML.
  • In Python: Pass a drive_cycles dictionary where values are 2-column NumPy arrays (time, value).
  • In YAML: Use the Drive direction. The value is the name of the drive cycle from the dictionary. The duration is defined by the time column in the data.
- US06 Drive Cycle:
    - Drive:
        mode: Current
        value: US06

Subroutines

Subroutines are reusable sequences of steps defined in your Python code and called from the YAML protocol. This is useful for standard procedures like a CCCV charge.
  • In Python: Pass a subroutines dictionary to solve_protocol. The values are lists of steps.
  • In YAML: Use the Subroutine step type with the name of the subroutine to execute.
# In your main protocol.yaml file
steps:
  - Initial Rest:
      - Rest:
          duration: 1800

  - "Increment cycle number"
  - Subroutine: CCCV # This calls the "CCCV" subroutine defined in Python

Output

The protocol solver returns a pandas DataFrame containing the results of the simulation, including time, voltage, current, temperature, cycle number, and any custom variables defined with set_variable. For EIS steps, the output DataFrame includes frequency-domain impedance data instead of time-domain data:
ColumnDescription
Frequency [Hz]Excitation frequency
Z_Re [Ohm]Real part of impedance
Z_Im [Ohm]Imaginary part of impedance (negative for capacitive behavior)
During EIS steps, time-domain columns (Time [s], Voltage [V], Current [A]) are present but contain NaN values. The Step count column identifies which step each row belongs to, allowing you to separate EIS data from cycling data.