nibcq.ParallelEIS

class nibcq.ParallelEIS(leader: nibcq._device.Device, followers: collections.abc.Sequence[nibcq._device.Device], test_parameters: nibcq._eis.EISTestParameters, multi_device_type: nibcq.enums.MultiDeviceMode = MultiDeviceMode.PARALLEL)

Bases: nibcq._acir_parallel.ParallelACIR, nibcq._eis.EIS

Defines an EIS measurement with multiple SMUs working in parallel.

This class enables multi-frequency electrochemical impedance spectroscopy using multiple SMU devices connected in parallel. It combines the parallel hardware management from ParallelACIR (trigger synchronization, current division, multidevice data collection) with the frequency sweep workflow from EIS.

Each frequency point in the sweep is essentially a parallel ACIR measurement: all devices are configured, synchronized, and measured together, then the per-device currents are summed and voltage is taken from the leader. This is repeated for every frequency in the sweep (highest to lowest).

Unlike single-device EIS measurements, parallel EIS does NOT support switching configurations due to hardware current limitations of switch matrices (typically limited to ~2A). Parallel EIS is designed for direct-connection, high-current testing scenarios.

Key Features:
  • Multiple SMUs synchronized via hardware triggers (PXI backplane).

  • Current capacity scales linearly with number of devices.

  • Full frequency sweep with per-frequency current amplitude support.

  • Voltage measured from leader device (4-wire remote sense).

  • Current contributions summed across all devices at each frequency.

  • Bode and Nyquist plot generation from sweep results.

Hardware Requirements:
  • All devices must be NI PXIe-4139 or compatible SMUs.

  • Devices must be in the same PXI chassis for trigger routing.

  • One device designated as leader, others as followers.

Inheritance:

ParallelEIS inherits from both ParallelACIR and EIS.

  • ParallelACIR provides parallel hardware management, multidevice data, and compensation.

  • EIS provides the frequency sweep workflow, plotting, and list-based results.

Example

>>> leader = Device.create(DeviceFamily.SMU, "PXI1Slot2")
>>> follower1 = Device.create(DeviceFamily.SMU, "PXI1Slot3")
>>>
>>> sweep = {
...     1000.0: FrequencySet(current_amplitude=3.0, number_of_periods=20),
...     100.0: FrequencySet(current_amplitude=3.0, number_of_periods=50),
... }
>>> params = EISTestParameters(frequency_sweep_characteristics=sweep)
>>>
>>> eis = ParallelEIS(leader=leader, followers=[follower1], test_parameters=params)
>>> compensation = eis.load_compensation_file()
>>> results = eis.run(compensation)
>>> nyquist, bode_mag, bode_phase = eis.get_plots()
Parameters:
test_parameters

Get the current test parameters for the measurement.

Returns the configuration parameters that define how the measurement should be performed, including settings like powerline frequency and other measurement-specific parameters.

Returns:

The current test parameters configuration

Return type:

TestParameters

Examples

>>> measurement = Measurement(device)
>>> params = measurement.test_parameters
>>> print(params.powerline_frequency)
PowerlineFrequency.FREQ_60_HZ
run(compensation: nibcq.compensation.Compensation, measurement_callback: Callable[[nibcq.measurement.SMUMeasurement], None] | None = None) list[nibcq._acir.SMUResult]

Execute a complete parallel EIS frequency sweep.

Performs impedance spectroscopy across all frequencies defined in the test parameters, using multiple synchronized SMU devices. Each frequency point follows the ParallelACIR measurement pattern: 1. Configure all devices for the current frequency. 2. Initiate synchronized measurement (leader triggers followers). 3. Fetch and aggregate multidevice data (leader voltage, summed currents). 4. DFT-based impedance calculation on the multidevice waveform. 5. Apply frequency-specific compensation.

Frequencies are swept from highest to lowest (EIS convention).

Parameters:
  • compensation (Compensation) – Compensation data for error correction across all frequencies.

  • measurement_callback (Callable or None) – Optional callback invoked after each frequency point with the raw multidevice SMUMeasurement data. Useful for progress tracking or raw data logging.

Returns:

Impedance results for each frequency point, containing

compensated impedance magnitude, phase, real and imaginary components.

Return type:

list[SMUResult]

Raises:

Example

>>> compensation = parallel_eis.load_compensation_file()
>>> results = parallel_eis.run(compensation)
>>> for r in results:
...     print(f"{r.measured_frequency:.1f} Hz: Z={r.z:.4f} Ohm")
write_compensation_file(compensation_file_path: str | None = None, kit_file_path: str | None = None, comment: str | None = None, measurement_callback: Callable[[nibcq.measurement.SMUMeasurement], None] | None = None) str

Create a compensation file across all frequencies in the sweep.

Performs the same per-frequency measurement loop as run(), but stores the raw impedance phasor at each frequency instead of compensating it. These raw impedance values form the compensation data that will be subtracted from future measurements.

All parallel device sessions are locked for the entire sweep to prevent concurrent access.

Parameters:
  • compensation_file_path (str or None) – Optional file path for the compensation data. If None, auto-generates path using leader serial number.

  • kit_file_path (str or None) – Optional path to known impedance table for KIT compensation subtraction (applied after all measurements).

  • comment (str or None) – Optional comment to embed in the compensation file.

  • measurement_callback (Callable or None) – Optional callback invoked after each frequency measurement with the raw multidevice SMUMeasurement data.

Returns:

Path to the created compensation file.

Return type:

str

Raises:
abstractmethod run_with_switching(compensation=None, measurement_callback=None)

Parallel EIS does not support switching.

Switching support is limited to 3A, which a single SMU can already provide. For current targets up to 3A, use single-device EIS with switching instead.

Raises:

NotImplementedError – Always raised.

current_frequency = 1000.0

Get the current frequency of the ACIR Measurement.

Returns:

The current configured frequency in Hz

Return type:

float

Raises:

FrequencyError – If frequency has not been set

property all_measurement_data: collections.abc.Iterator[tuple[nibcq._device.Device, nibcq.measurement.SMUMeasurement | None]]

Get raw measurement data from all individual devices.

Returns per-device voltage and current measurements as captured during the measurement phase, before the parallel multidevice operation is applied.

Returns:

Iterator of tuples pairing each

Device with its corresponding SMUMeasurement. Yields tuples with None measurements if no measurement has been performed yet.

Return type:

Iterator[tuple[Device, SMUMeasurement | None]]

Example

>>> for device, measurement in parallel_acir.all_measurement_data:
...     print(f"Device: {device.product}")
...     print(f"Voltage samples: {len(measurement.voltage_values)}")
property calibration_file_paths: list[tuple[nibcq._device.Device, str]]

Get the default calibration file paths for all parallel devices.

Returns a list of tuples pairing each Device with its default calibration diary file path. Calibration is managed per-device via the Calibrator class, so this property simply aggregates the paths.

Returns:

List of (device, path) tuples.

Return type:

list[tuple[Device, str]]

property measurement_data: nibcq.measurement.SMUMeasurement

Get sanitized multidevice measurement data after parallel combination.

Applies the same signal conditioning as single-device ACIR. First, edge effects are removed for frequencies above 60 Hz with more than 10 periods by trimming the first two periods from the signal. Then DC offset is removed by subtracting the mean from both voltage and current waveforms to center them around zero.

In parallel mode, voltage values come from the leader device (4-wire remote sense) and current values are the sum of all devices’ individual currents.

Returns:

Sanitized combined measurement data, or None if

no multidevice data is available yet.

Return type:

SMUMeasurement

generate_compensation_file_path() str

Generate compensation file path for parallel ACIR measurement.

Uses the leader device’s serial number with parallel-specific naming: z_{method}_Leader_SN{serial_number}.json

This differs from single-device ACIR which uses: z_{method}_{serial_number}.json

The parallel naming convention makes it clear which device was the leader during compensation data collection.

Returns:

The generated file path for compensation data, or None if no

compensation is needed.

Return type:

str

Raises:

CompensationMethodError – If compensation_method is not supported.

get_all_devices()

Yield every parallel device, followers first, leader last.

This generator avoids storing a redundant copy of the device references that already live in ParallelMeasurement’s fields containing the followers and the leader.

Yields:

Device – The next device in followers-then-leader order.

property result: float | SMUResult | list[SMUResult] | list[tuple[nibcq.switch.SMUCellData, SMUResult]] | list[tuple[nibcq.switch.SMUCellData, list[SMUResult]]] | list[tuple[str, tuple[datetime.datetime, datetime.datetime, float]]] | Any

Get the result of the last measurement.

Returns the measurement result from the most recent measurement operation. The result type depends on the specific measurement implementation and whether switching was used:

  • Single measurements:
    • float for OCV and DCIR

    • SMUResult for ACIR

    • list[SMUResult] for EIS

  • Switching measurements:
    • list[tuple[SMUCellData, SMUResult]] for ACIR with switching

    • list[tuple[SMUCellData, list[SMUResult]]], for EIS with switching

    • list[tuple[str, tuple[datetime, datetime, float]]], for OCV with switching

Returns:

float | SMUResult | list[SMUResult] | list[tuple[SMUCellData, SMUResult]] | list[tuple[SMUCellData, list[SMUResult]]] | list[tuple[str, tuple[datetime, datetime, float]]] | Any: The measurement result(s). For switching measurements, returns a list of tuples where each tuple contains the cell data and its corresponding results.

Raises:

RuntimeError – If no measurement has been performed yet or if the result is not available

Return type:

Union[float, SMUResult, list[SMUResult], list[tuple[nibcq.switch.SMUCellData, SMUResult]], list[tuple[nibcq.switch.SMUCellData, list[SMUResult]]], list[tuple[str, tuple[datetime.datetime, datetime.datetime, float]]], Any]

Examples

>>> measurement.run(test_parameters)
>>> result = measurement.result
>>> print(f"Measurement result: {result}")
>>>
>>> # For switching measurements
>>> switching_results = measurement.run_with_switching(compensation)
>>> for cell_data, cell_results in measurement.result:
...     print(f"Cell {cell_data.cell_serial_number}: {len(cell_results)} results")
DEVICE_FAMILY: nibcq.enums.DeviceFamily = None

Device family for ACIR and similar measurement types.

Type:

DeviceFamily

property acceptable_temperature_delta: float

Get the acceptable temperature delta for compensation validation.

Returns the maximum allowed temperature difference from the device’s temperature capability. This is a pass-through property that delegates to the underlying TemperatureCapability.

Returns:

The acceptable temperature delta in degrees, or NaN if no temperature capability

Return type:

float

Examples

>>> measurement = EIS(device)
>>> measurement.acceptable_temperature_delta = 2.5
>>> delta = measurement.acceptable_temperature_delta
property temperature: float

Get the latest temperature reading from the device.

Returns:

The most recent temperature measurement, or NaN if no temperature capability

Return type:

float

property temperature_range: CenteredRange

Get the latest temperature reading from the device, coupled with the user-set delta.

Returns:

A CenteredRange representing the most recent temperature measurement (NaN if not available), along with the acceptable temperature delta (NaN if not set).

Return type:

CenteredRange

measure_temperature() CenteredRange

Get a new temperature reading from the device.

Returns:

Current temperature reading, or NaN if no temperature capability

Return type:

CenteredRange

validate_temperature(target_temperature: CenteredRange) bool

Validate the current temperature against the compensation file’s target.

Delegates to the device’s temperature capability for validation. The capability handles all validation logic including checking if thermocouple is configured, using overridden delta values if set, and printing appropriate warnings.

Parameters:

target_temperature (CenteredRange) – The target temperature parameters for validation

Returns:

True if thermocouple is configured and temperature is within range.

False if thermocouple is not configured (capability missing or not set up), or if target temperature/delta is NaN.

Return type:

bool

Raises:

TemperatureError – If the current temperature exceeds the target ± delta range (only raised when capability is configured)

Examples

>>> measurement = EIS(device)
>>> measurement.measure_temperature()
>>> target = compensation.temperature_parameter
>>> is_valid = measurement.validate_temperature(target)
FREQUENCY_LIMIT: Final[float] = 10000.0

Frequency limit for ACIR and similar measurement types.

Type:

float

SENSITIVITY_FREQUENCY_LIMIT: Final[float] = 7500.0

Frequency limit for DEVICE_CURRENT_LIMIT A Current measurements.

Type:

float

CURRENT_LIMIT: Final[float] = 2.0

High Frequency current limit for ACIR and similar measurement types.

Type:

float

DEVICE_CURRENT_LIMIT: Final[float] = 3.0

Moderate Frequency current limit for ACIR and similar measurement types.

Type:

float

property raw_data: nibcq.measurement.SMUMeasurement

Get the raw measurement data read from the device.

Only allows measurement to be read, not access it directly. Returns unprocessed data as captured from the device.

Returns:

Raw measurement data containing tone frequency, voltage values,

and current values

Return type:

SMUMeasurement

Raises:

SMUParameterError – If no measurement data is available or measurement is incomplete

static validate_current_amplitude(target_frequency: float, current_amplitude: float) bool

Validate that the current amplitude is within acceptable limits.

Parameters:
  • target_frequency (float) – The target frequency in Hz. Determines which current limit applies (CURRENT_LIMIT above SENSITIVITY_FREQUENCY_LIMIT, DEVICE_CURRENT_LIMIT at or below).

  • current_amplitude (float) – The current amplitude to validate (in Amps).

Returns:

Always returns True when validation passes.

Return type:

bool

Raises:

CurrentAmplitudeError – If the current amplitude is not positive or exceeds the defined maximum limit (defined in ACIR.CURRENT_LIMIT constant).

load_compensation_file(file_path: str | None = None) nibcq.compensation.Compensation

Load compensation file based on compensation method and device serial number.

Creates and returns a compensation object with the appropriate compensation data. For NO_COMPENSATION, creates a default compensation object. For other methods, loads compensation data from file.

Parameters:

file_path (str, optional) – Optional specific file path to read from. If None, generates path automatically

Returns:

The loaded compensation object

Return type:

Compensation

Raises:
  • FileNotFoundError – If compensation file is not found and compensation is required

  • ValueError – If compensation file is invalid and compensation is required

  • CompensationMethodError – If compensation method is not supported by the nibcq Python API

property has_switch_capability: bool

Check if the device has switch capability.

Returns:

True if switch capability is available, False otherwise

Return type:

bool

connect_channel(channel: SMUCellData) None

Connect to a specific DUT channel using the device’s switch capability.

Parameters:

channel (SMUCellData) – SwitchChannel containing connection information

Raises:

RuntimeError – If no switch capability is available

Return type:

None

disconnect_all() None

Disconnect all channels using the device’s switch capability.

Raises:

RuntimeError – If no switch capability is available

Return type:

None

wait_for_debounce() None

Wait for switch relays to settle using the device’s switch capability.

Raises:

RuntimeError – If no switch capability is available

Return type:

None

property switch_cells: List[str] | List[SMUCellData]

Get the configured switch cells from the device.

Returns:

DUT channel names or SMUCellData objects, which contain the DUT and switch channel information.

Return type:

list[str] | list[SMUCellData]

property frequency_list

Get the frequency sweep frequencies as a descending sorted list.

Returns all frequencies defined in the frequency sweep characteristics, sorted in descending order (highest to lowest frequency). This order is typically used in EIS measurements to start with high frequencies and sweep down to low frequencies.

Returns:

A list of frequencies in Hz, sorted in descending order

Return type:

list[float]

Raises:

FrequencyError – If frequency sweep characteristics are not set or empty

Examples

>>> eis.frequency_list
[1000.0, 500.0, 100.0, 50.0, 10.0]
get_plots() tuple[PlotSeries, PlotSeries, PlotSeries] | list[tuple[PlotSeries, PlotSeries, PlotSeries]]

Return plotting datasets for Nyquist and Bode plots.

For single device measurements, returns a 3-tuple:
  • (nyquist_x, nyquist_y): Nyquist (Cole) data where nyquist_x is R and nyquist_y is -X (both lists of floats) sorted by ascending frequency.

  • (freqs, magnitudes): Bode magnitude data (frequency ascending, abs(Z)).

  • (freqs, phases): Bode phase data (frequency ascending, theta in degrees).

For switching measurements, returns a list of 3-tuples, one for each cell.

The method reads measurement results and sorts the returned points by frequency (ascending) to make plotting predictable for common plotting conventions.

Raises:

RuntimeError – If no measurement results are available.

Return type:

tuple[PlotSeries, PlotSeries, PlotSeries] | list[tuple[PlotSeries, PlotSeries, PlotSeries]]