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.
There is currently no way to create a new UCP file from the UI - it gets created by converting a commercial protocol into a UCP file. This page is for information on the underlying UCP file format.

Global Configuration

You can define global settings for the entire protocol under the optional global key.
global:
  temperature: 25
  initial_soc: 100
  # You can also define the initial state by voltage.
  # initial_voltage: 4.2
  resolution:
    time: 60
  • temperature: The ambient temperature for the experiment in Celsius. Defaults to 25.
  • initial_soc: The initial State of Charge in percent (e.g., 100).
  • initial_voltage: The initial voltage for the experiment.
  • resolution: The time resolution for the simulation output in seconds. Defaults to 60.
initial_soc and initial_voltage are mutually exclusive. If neither is provided, initial_soc defaults to 100%.

Safety Limits

You can define optional safety limits for the simulation. If any of these are breached, the current simulation step will terminate.
safety_limits:
  voltage_min: 2.5
  voltage_max: 4.2
  temperature_min: -20
  temperature_max: 60
  charge_current_max: 10 # Max charge current in Amps (positive value)
  discharge_current_max: 15 # Max discharge current in Amps (positive value)
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.

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"
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).
  • Simulation Results: Voltage, Current, etc. from the previous step.
  • Helper Functions: first(), last(), mean(), abs(), ifelse(), etc.

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.