Skip to content

API Reference

A universal cycling protocol which can be converted to different formats.

Create a CyclingProtocol object directly, or create from a dict or JSON file.

Convert to different formats e.g. .to_neware_xml() or to_biologic_mps().

ConstantCurrent

Bases: Step

Constant current step.

At least one of rate_C or current_mA must be set. If rate_C is used, a sample capacity must be set in the CyclingProtocol, and it will take priority over current_mA.

The termination ('until') conditions are OR conditions, the step will end when any one of these is met.

Attributes:

Name Type Description
rate_C float | None

(optional) The current applied in C-rate units (i.e. mA per mAh).

current_mA float | None

(optional) The current applied in mA.

until_time_s float | None

Duration of step in seconds.

until_voltage_V float | None

End step when this voltage in V is reached.

Source code in aurora_unicycler\_core.py
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
class ConstantCurrent(Step):
    """Constant current step.

    At least one of `rate_C` or `current_mA` must be set. If `rate_C` is used, a
    sample capacity must be set in the CyclingProtocol, and it will take priority over
    `current_mA`.

    The termination ('until') conditions are OR conditions, the step will end
    when any one of these is met.

    Attributes:
        rate_C: (optional) The current applied in C-rate units (i.e. mA per mAh).
        current_mA: (optional) The current applied in mA.
        until_time_s: Duration of step in seconds.
        until_voltage_V: End step when this voltage in V is reached.

    """

    step: Literal["constant_current"] = Field(default="constant_current", frozen=True)
    rate_C: float | None = None
    current_mA: float | None = None
    until_time_s: float | None = None
    until_voltage_V: float | None = None

    @field_validator("rate_C", mode="before")
    @classmethod
    def _parse_c_rate(cls, v: float | str) -> float | None:
        """C-rate can be a string e.g. "C/2"."""
        return _coerce_c_rate(v)

    @field_validator("current_mA", "until_time_s", "until_voltage_V", mode="before")
    @classmethod
    def _allow_empty_string(cls, v: float | str) -> float | None:
        """Empty string is interpreted as None."""
        return _empty_string_is_none(v)

    @model_validator(mode="after")
    def _ensure_rate_or_current(self) -> Self:
        """Ensure at least one of rate_C or current_mA is set."""
        has_rate_C = self.rate_C is not None and self.rate_C != 0
        has_current_mA = self.current_mA is not None and self.current_mA != 0
        if not (has_rate_C or has_current_mA):
            msg = "Either rate_C or current_mA must be set and non-zero."
            raise ValueError(msg)
        return self

    @model_validator(mode="after")
    def _ensure_stop_condition(self) -> Self:
        """Ensure at least one stop condition is set."""
        has_time_s = self.until_time_s is not None and self.until_time_s != 0
        has_voltage_V = self.until_voltage_V is not None and self.until_voltage_V != 0
        if not (has_time_s or has_voltage_V):
            msg = "Either until_time_s or until_voltage_V must be set and non-zero."
            raise ValueError(msg)
        return self

ConstantVoltage

Bases: Step

Constant voltage step.

The termination ('until') conditions are OR conditions, the step will end when any one of these is met. If both until_rate_C and until_current_mA are set, C-rate will take priority.

Note that in most cyclers, a voltage is not applied directly, instead the current is adjusted to achieve a certain voltage.

Attributes:

Name Type Description
voltage_V float

The voltage applied in V.

until_time_s float | None

Duration of step in seconds.

until_rate_C float | None

End step when this C-rate (i.e. mA per mAh) is reached.

until_current_mA float | None

End step when this current in mA is reached.

Source code in aurora_unicycler\_core.py
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
class ConstantVoltage(Step):
    """Constant voltage step.

    The termination ('until') conditions are OR conditions, the step will end
    when any one of these is met. If both `until_rate_C` and `until_current_mA`
    are set, C-rate will take priority.

    Note that in most cyclers, a voltage is not applied directly, instead the
    current is adjusted to achieve a certain voltage.

    Attributes:
        voltage_V: The voltage applied in V.
        until_time_s: Duration of step in seconds.
        until_rate_C: End step when this C-rate (i.e. mA per mAh) is reached.
        until_current_mA: End step when this current in mA is reached.

    """

    step: Literal["constant_voltage"] = Field(default="constant_voltage", frozen=True)
    voltage_V: float
    until_time_s: float | None = None
    until_rate_C: float | None = None
    until_current_mA: float | None = None

    @field_validator("until_rate_C", mode="before")
    @classmethod
    def _parse_c_rate(cls, v: float | str) -> float | None:
        """C-rate can be a string e.g. "C/2"."""
        return _coerce_c_rate(v)

    @field_validator("voltage_V", "until_time_s", "until_current_mA", mode="before")
    @classmethod
    def _allow_empty_string(cls, v: float | str) -> float | None:
        """Empty string is interpreted as None."""
        return _empty_string_is_none(v)

    @model_validator(mode="after")
    def _check_stop_condition(self) -> Self:
        """Ensure at least one of until_rate_C or until_current_mA is set."""
        has_time_s = self.until_time_s is not None and self.until_time_s != 0
        has_rate_C = self.until_rate_C is not None and self.until_rate_C != 0
        has_current_mA = self.until_current_mA is not None and self.until_current_mA != 0
        if not (has_time_s or has_rate_C or has_current_mA):
            msg = "Either until_time_s, until_rate_C, or until_current_mA must be set and non-zero."
            raise ValueError(msg)
        return self

CyclingProtocol

Bases: BaseProtocol

Unicycler battery cycling protocol.

Defines a battery cycling experiment, which can be converted to different formats for different cycler machine vendors.

Source code in aurora_unicycler\protocol.py
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
class CyclingProtocol(BaseProtocol):
    """Unicycler battery cycling protocol.

    Defines a battery cycling experiment, which can be converted to different formats for different
    cycler machine vendors.
    """

    # Add conversion methods to the base class, include docstrings here so mkdocs work
    def to_battinfo_jsonld(
        self,
        save_path: Path | str | None = None,
        capacity_mAh: float | None = None,
        *,
        include_context: bool = True,
    ) -> dict:
        """Convert protocol to BattInfo JSON-LD format.

        This generates the 'hasTask' key in BattINFO, and does not include the
        creator, lab, instrument etc.

        Args:
            save_path: (optional) File path of where to save the JSON-LD file.
            capacity_mAh: (optional) Override the protocol sample capacity.
            include_context: (optional) Add a `@context` key to the root of the
                JSON-LD.

        Returns:
            Dictionary representation of the JSON-LD.

        """
        return battinfo.to_battinfo_jsonld(
            self,
            save_path,
            capacity_mAh,
            include_context=include_context,
        )

    def to_biologic_mps(
        self,
        save_path: Path | str | None = None,
        sample_name: str | None = None,
        capacity_mAh: float | None = None,
        range_V: tuple[float, float] = (0.0, 5.0),
    ) -> str:
        """Convert protocol to a Biologic Settings file (.mps).

        Uses the ModuloBatt technique.

        Note that you must add OCV steps inbetween CC/CV steps if you want the
        current range to be able to change.

        Args:
            save_path: (optional) File path of where to save the mps file.
            sample_name: (optional) Override the protocol sample name.
            capacity_mAh: (optional) Override the protocol sample capacity.
            range_V: (optional) Voltage range of instrument in volts, default 0-5 V.
                Usually capped at +- 10 V. Smaller ranges can improve resolution.

        Returns:
            mps string representation of the protocol.

        """
        return biologic.to_biologic_mps(self, save_path, sample_name, capacity_mAh, range_V)

    def to_neware_xml(
        self,
        save_path: Path | str | None = None,
        sample_name: str | None = None,
        capacity_mAh: float | None = None,
    ) -> str:
        """Convert the protocol to Neware XML format.

        Args:
            save_path: (optional) File path of where to save the xml file.
            sample_name: (optional) Override the protocol sample name. A sample
                name must be provided in this function. It is stored as the
                'barcode' of the Neware protocol.
            capacity_mAh: (optional) Override the protocol sample capacity.

        Returns:
            xml string representation of the protocol.

        """
        return neware.to_neware_xml(self, save_path, sample_name, capacity_mAh)

    def to_pybamm_experiment(self) -> list[str]:
        """Convert protocol to PyBaMM experiment format.

        A PyBaMM experiment does not need capacity or sample name.

        Returns:
            list of strings representing the PyBaMM experiment.

        """
        return pybamm.to_pybamm_experiment(self)

    def to_tomato_mpg2(
        self,
        save_path: Path | str | None = None,
        tomato_output: Path = Path("C:/tomato_data/"),
        sample_name: str | None = None,
        capacity_mAh: float | None = None,
    ) -> str:
        """Convert protocol to tomato 0.2.3 + MPG2 compatible JSON format.

        Args:
            save_path: (optional) File path of where to save the json file.
            tomato_output: (optional) Where to save the data from tomato.
            sample_name: (optional) Override the protocol sample name.
            capacity_mAh: (optional) Override the protocol sample capacity.

        Returns:
            json string representation of the protocol.

        """
        return tomato.to_tomato_mpg2(self, save_path, tomato_output, sample_name, capacity_mAh)

to_battinfo_jsonld(save_path=None, capacity_mAh=None, *, include_context=True)

Convert protocol to BattInfo JSON-LD format.

This generates the 'hasTask' key in BattINFO, and does not include the creator, lab, instrument etc.

Parameters:

Name Type Description Default
save_path Path | str | None

(optional) File path of where to save the JSON-LD file.

None
capacity_mAh float | None

(optional) Override the protocol sample capacity.

None
include_context bool

(optional) Add a @context key to the root of the JSON-LD.

True

Returns:

Type Description
dict

Dictionary representation of the JSON-LD.

Source code in aurora_unicycler\protocol.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
def to_battinfo_jsonld(
    self,
    save_path: Path | str | None = None,
    capacity_mAh: float | None = None,
    *,
    include_context: bool = True,
) -> dict:
    """Convert protocol to BattInfo JSON-LD format.

    This generates the 'hasTask' key in BattINFO, and does not include the
    creator, lab, instrument etc.

    Args:
        save_path: (optional) File path of where to save the JSON-LD file.
        capacity_mAh: (optional) Override the protocol sample capacity.
        include_context: (optional) Add a `@context` key to the root of the
            JSON-LD.

    Returns:
        Dictionary representation of the JSON-LD.

    """
    return battinfo.to_battinfo_jsonld(
        self,
        save_path,
        capacity_mAh,
        include_context=include_context,
    )

to_biologic_mps(save_path=None, sample_name=None, capacity_mAh=None, range_V=(0.0, 5.0))

Convert protocol to a Biologic Settings file (.mps).

Uses the ModuloBatt technique.

Note that you must add OCV steps inbetween CC/CV steps if you want the current range to be able to change.

Parameters:

Name Type Description Default
save_path Path | str | None

(optional) File path of where to save the mps file.

None
sample_name str | None

(optional) Override the protocol sample name.

None
capacity_mAh float | None

(optional) Override the protocol sample capacity.

None
range_V tuple[float, float]

(optional) Voltage range of instrument in volts, default 0-5 V. Usually capped at +- 10 V. Smaller ranges can improve resolution.

(0.0, 5.0)

Returns:

Type Description
str

mps string representation of the protocol.

Source code in aurora_unicycler\protocol.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
def to_biologic_mps(
    self,
    save_path: Path | str | None = None,
    sample_name: str | None = None,
    capacity_mAh: float | None = None,
    range_V: tuple[float, float] = (0.0, 5.0),
) -> str:
    """Convert protocol to a Biologic Settings file (.mps).

    Uses the ModuloBatt technique.

    Note that you must add OCV steps inbetween CC/CV steps if you want the
    current range to be able to change.

    Args:
        save_path: (optional) File path of where to save the mps file.
        sample_name: (optional) Override the protocol sample name.
        capacity_mAh: (optional) Override the protocol sample capacity.
        range_V: (optional) Voltage range of instrument in volts, default 0-5 V.
            Usually capped at +- 10 V. Smaller ranges can improve resolution.

    Returns:
        mps string representation of the protocol.

    """
    return biologic.to_biologic_mps(self, save_path, sample_name, capacity_mAh, range_V)

to_neware_xml(save_path=None, sample_name=None, capacity_mAh=None)

Convert the protocol to Neware XML format.

Parameters:

Name Type Description Default
save_path Path | str | None

(optional) File path of where to save the xml file.

None
sample_name str | None

(optional) Override the protocol sample name. A sample name must be provided in this function. It is stored as the 'barcode' of the Neware protocol.

None
capacity_mAh float | None

(optional) Override the protocol sample capacity.

None

Returns:

Type Description
str

xml string representation of the protocol.

Source code in aurora_unicycler\protocol.py
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
def to_neware_xml(
    self,
    save_path: Path | str | None = None,
    sample_name: str | None = None,
    capacity_mAh: float | None = None,
) -> str:
    """Convert the protocol to Neware XML format.

    Args:
        save_path: (optional) File path of where to save the xml file.
        sample_name: (optional) Override the protocol sample name. A sample
            name must be provided in this function. It is stored as the
            'barcode' of the Neware protocol.
        capacity_mAh: (optional) Override the protocol sample capacity.

    Returns:
        xml string representation of the protocol.

    """
    return neware.to_neware_xml(self, save_path, sample_name, capacity_mAh)

to_pybamm_experiment()

Convert protocol to PyBaMM experiment format.

A PyBaMM experiment does not need capacity or sample name.

Returns:

Type Description
list[str]

list of strings representing the PyBaMM experiment.

Source code in aurora_unicycler\protocol.py
139
140
141
142
143
144
145
146
147
148
def to_pybamm_experiment(self) -> list[str]:
    """Convert protocol to PyBaMM experiment format.

    A PyBaMM experiment does not need capacity or sample name.

    Returns:
        list of strings representing the PyBaMM experiment.

    """
    return pybamm.to_pybamm_experiment(self)

to_tomato_mpg2(save_path=None, tomato_output=Path('C:/tomato_data/'), sample_name=None, capacity_mAh=None)

Convert protocol to tomato 0.2.3 + MPG2 compatible JSON format.

Parameters:

Name Type Description Default
save_path Path | str | None

(optional) File path of where to save the json file.

None
tomato_output Path

(optional) Where to save the data from tomato.

Path('C:/tomato_data/')
sample_name str | None

(optional) Override the protocol sample name.

None
capacity_mAh float | None

(optional) Override the protocol sample capacity.

None

Returns:

Type Description
str

json string representation of the protocol.

Source code in aurora_unicycler\protocol.py
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
def to_tomato_mpg2(
    self,
    save_path: Path | str | None = None,
    tomato_output: Path = Path("C:/tomato_data/"),
    sample_name: str | None = None,
    capacity_mAh: float | None = None,
) -> str:
    """Convert protocol to tomato 0.2.3 + MPG2 compatible JSON format.

    Args:
        save_path: (optional) File path of where to save the json file.
        tomato_output: (optional) Where to save the data from tomato.
        sample_name: (optional) Override the protocol sample name.
        capacity_mAh: (optional) Override the protocol sample capacity.

    Returns:
        json string representation of the protocol.

    """
    return tomato.to_tomato_mpg2(self, save_path, tomato_output, sample_name, capacity_mAh)

ImpedanceSpectroscopy

Bases: Step

Electrochemical Impedance Spectroscopy (EIS) step.

Only one of amplitude_V (PEIS) or amplitude_mA (GEIS) can be set.

Attributes:

Name Type Description
amplitude_V float | None

(optional) Oscillation amplitude in V.

amplitude_mA float | None

(optional) Oscillation amplitude in mA.

start_frequency_Hz float

Beginning frequency in Hz.

end_frequency_Hz float

End frequency in Hz.

points_per_decade int

How many points to measure per decade, i.e. power of 10.

measures_per_point int

How many measurements to average per point.

drift_correction bool | None

Corrects for drift in the system - requires twice as many measurements. Compensates measured current/voltage at frequency f_m with pointsf_m-1 and f_m+1 using the formula for PEIS ∆I(f_m) = I(f_m) + (I(f_m+1) - I(f_m-1))/2, (and similar for V in GEIS). Operates on both real and imaginary parts.

Source code in aurora_unicycler\_core.py
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
class ImpedanceSpectroscopy(Step):
    """Electrochemical Impedance Spectroscopy (EIS) step.

    Only one of `amplitude_V` (PEIS) or `amplitude_mA` (GEIS) can be set.

    Attributes:
        amplitude_V: (optional) Oscillation amplitude in V.
        amplitude_mA: (optional) Oscillation amplitude in mA.
        start_frequency_Hz: Beginning frequency in Hz.
        end_frequency_Hz: End frequency in Hz.
        points_per_decade: How many points to measure per decade, i.e. power of 10.
        measures_per_point: How many measurements to average per point.
        drift_correction: Corrects for drift in the system - requires twice as
            many measurements. Compensates measured current/voltage at frequency
            `f_m` with points`f_m-1` and `f_m+1` using the formula for PEIS
            `∆I(f_m) = I(f_m) + (I(f_m+1) - I(f_m-1))/2`, (and similar for V in
            GEIS). Operates on both real and imaginary parts.

    """

    step: Literal["impedance_spectroscopy"] = Field(default="impedance_spectroscopy", frozen=True)
    amplitude_V: float | None = None
    amplitude_mA: float | None = None
    start_frequency_Hz: float = Field(ge=1e-5, le=1e5, description="Start frequency in Hz")
    end_frequency_Hz: float = Field(ge=1e-5, le=1e5, description="End frequency in Hz")
    points_per_decade: int = Field(gt=0, default=10)
    measures_per_point: int = Field(gt=0, default=1)
    drift_correction: bool | None = Field(default=False, description="Apply drift correction")
    model_config = ConfigDict(extra="forbid")

    @field_validator("amplitude_V", "amplitude_mA", mode="before")
    @classmethod
    def _allow_empty_string(cls, v: float | str) -> float | None:
        """Empty string is interpreted as None."""
        return _empty_string_is_none(v)

    @model_validator(mode="after")
    def _validate_amplitude(self) -> Self:
        """Cannot set both amplitude_V and amplitude_mA."""
        if self.amplitude_V is not None and self.amplitude_mA is not None:
            msg = "Cannot set both amplitude_V and amplitude_mA."
            raise ValueError(msg)
        if self.amplitude_V is None and self.amplitude_mA is None:
            msg = "Either amplitude_V or amplitude_mA must be set."
            raise ValueError(msg)
        return self

Loop

Bases: Step

Loop step.

Supports both looping to a tag or the step number (1-indexed). It is recommened to use tags to avoid potential errors with indexing or when adding/removing steps.

Internally, tags are converted to indexes with the correct indexing when sending to cyclers.

Attributes:

Name Type Description
loop_to Annotated[int | str, Field()]

The tag or step number (1-indexed) to loop back to.

cycle_count int

How many times to loop. This is the TOTAL number of cycles. Different cyclers define this differently. Here, a cycle_count of 3 means 3 cycles in total will be performed.

Source code in aurora_unicycler\_core.py
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
class Loop(Step):
    """Loop step.

    Supports both looping to a tag or the step number (1-indexed). It is
    recommened to use tags to avoid potential errors with indexing or when
    adding/removing steps.

    Internally, tags are converted to indexes with the correct indexing when
    sending to cyclers.

    Attributes:
        loop_to: The tag or step number (1-indexed) to loop back to.
        cycle_count: How many times to loop. This is the TOTAL number of cycles.
            Different cyclers define this differently. Here, a cycle_count of 3
            means 3 cycles in total will be performed.

    """

    step: Literal["loop"] = Field(default="loop", frozen=True)
    loop_to: Annotated[int | str, Field()] = Field(default=1)
    cycle_count: int = Field(gt=0)
    model_config = ConfigDict(extra="forbid")

    @field_validator("loop_to")
    @classmethod
    def _validate_loop_to(cls, v: int | str) -> int | str:
        """Ensure loop_to is a positive integer or a string."""
        if isinstance(v, int) and v <= 0:
            msg = "Start step must be positive integer or a string"
            raise ValueError(msg)
        if isinstance(v, str) and v.strip() == "":
            msg = "Start step cannot be empty"
            raise ValueError(msg)
        return v

OpenCircuitVoltage

Bases: Step

Open circuit voltage step.

Attributes:

Name Type Description
until_time_s float

Duration of step in seconds.

Source code in aurora_unicycler\_core.py
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
class OpenCircuitVoltage(Step):
    """Open circuit voltage step.

    Attributes:
        until_time_s: Duration of step in seconds.

    """

    step: Literal["open_circuit_voltage"] = Field(default="open_circuit_voltage", frozen=True)
    until_time_s: float = Field(gt=0)

    @field_validator("until_time_s", mode="before")
    @classmethod
    def _allow_empty_string(cls, v: float | str) -> float | None:
        """Empty string is interpreted as None."""
        return _empty_string_is_none(v)

RecordParams

Bases: BaseModel

Recording parameters.

Attributes:

Name Type Description
current_mA Annotated[float, Field(gt=0)] | None

Current change in mA which triggers recording data.

voltage_V Annotated[float, Field(gt=0)] | None

Voltage change in V which triggers recording data.

time_s Annotated[float, Field(gt=0)]

Time in seconds between recording data.

Source code in aurora_unicycler\_core.py
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
class RecordParams(BaseModel):
    """Recording parameters.

    Attributes:
        current_mA: Current change in mA which triggers recording data.
        voltage_V: Voltage change in V which triggers recording data.
        time_s: Time in seconds between recording data.

    """

    current_mA: Annotated[float, Field(gt=0)] | None = None
    voltage_V: Annotated[float, Field(gt=0)] | None = None
    time_s: Annotated[float, Field(gt=0)]

    model_config = ConfigDict(extra="forbid")

SafetyParams

Bases: BaseModel

Safety parameters, i.e. limits before cancelling the entire experiment.

Attributes:

Name Type Description
max_voltage_V float | None

Maximum voltage in V.

min_voltage_V float | None

Minimum voltage in V.

max_current_mA float | None

Maximum current in mA.

min_current_mA float | None

Minimum current in mA (can be negative).

max_capacity_mAh float | None

Maximum capacity in mAh.

delay_s float | None

How long in seconds limits must be exceeded before cancelling.

Source code in aurora_unicycler\_core.py
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
class SafetyParams(BaseModel):
    """Safety parameters, i.e. limits before cancelling the entire experiment.

    Attributes:
        max_voltage_V: Maximum voltage in V.
        min_voltage_V: Minimum voltage in V.
        max_current_mA: Maximum current in mA.
        min_current_mA: Minimum current in mA (can be negative).
        max_capacity_mAh: Maximum capacity in mAh.
        delay_s: How long in seconds limits must be exceeded before cancelling.

    """

    max_voltage_V: float | None = None
    min_voltage_V: float | None = None
    max_current_mA: float | None = None
    min_current_mA: float | None = None
    max_capacity_mAh: float | None = Field(ge=0, default=None)
    delay_s: float | None = Field(ge=0, default=None)

    model_config = ConfigDict(extra="forbid")

    @model_validator(mode="after")
    def _validate_limits(self) -> Self:
        """Check max > min for current and voltage."""
        if (
            self.max_voltage_V is not None
            and self.min_voltage_V is not None
            and self.max_voltage_V <= self.min_voltage_V
        ):
            msg = "Max voltage must be larger than min voltage."
            raise ValueError(msg)
        if (
            self.max_current_mA is not None
            and self.min_current_mA is not None
            and self.max_current_mA <= self.min_current_mA
        ):
            msg = "Max current must be larger than min current."
            raise ValueError(msg)
        return self

SampleParams

Bases: BaseModel

Sample parameters.

Attributes:

Name Type Description
name str

Sample name.

capacity_mAh float | None

Sample capacity in mAh, used to calculate current from C-rates.

Source code in aurora_unicycler\_core.py
75
76
77
78
79
80
81
82
83
84
85
86
87
class SampleParams(BaseModel):
    """Sample parameters.

    Attributes:
        name: Sample name.
        capacity_mAh: Sample capacity in mAh, used to calculate current from C-rates.

    """

    name: str = Field(default="$NAME")
    capacity_mAh: float | None = Field(gt=0, default=None)

    model_config = ConfigDict(extra="forbid")

Step

Bases: BaseModel

Base class for all steps.

Source code in aurora_unicycler\_core.py
149
150
151
152
153
154
class Step(BaseModel):
    """Base class for all steps."""

    # optional id field
    id: str | None = Field(default=None, description="Optional ID for the technique step")
    model_config = ConfigDict(extra="forbid")

Tag

Bases: Step

Tag step.

Used in combination with the Loop step, e.g.

[
    Tag(tag="formation")
    # Your cycling steps here
    Loop(loop_to="formation", cycle_count=3)
]

This will loop over the cycling steps 3 times. Put the tag before the step you want to loop to.

Can also be used for comments or organisation, but note that it will only be stored in unicycler, when sending to e.g. Biologic or Neware, loops/tags are converted to indices and the tag steps are removed.

Attributes:

Name Type Description
tag str

The tag name.

Source code in aurora_unicycler\_core.py
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
class Tag(Step):
    """Tag step.

    Used in combination with the Loop step, e.g.
    ```
    [
        Tag(tag="formation")
        # Your cycling steps here
        Loop(loop_to="formation", cycle_count=3)
    ]
    ```

    This will loop over the cycling steps 3 times. Put the tag before the step
    you want to loop to.

    Can also be used for comments or organisation, but note that it will only be
    stored in unicycler, when sending to e.g. Biologic or Neware, loops/tags are
    converted to indices and the tag steps are removed.

    Attributes:
        tag: The tag name.

    """

    step: Literal["tag"] = Field(default="tag", frozen=True)
    tag: str = Field(default="")

    model_config = ConfigDict(extra="forbid")

    @field_validator("tag", mode="before")
    @classmethod
    def _dont_allow_empty(cls, v: str) -> str:
        """Empty string is interpreted as None."""
        if v.strip() == "":
            msg = "Tag must not be empty."
            raise ValueError(msg)
        return v