nibcq.ParallelEIS ================= .. py: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: :py:obj:`nibcq._acir_parallel.ParallelACIR`, :py:obj:`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. .. rubric:: 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() .. py:attribute:: 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 :rtype: TestParameters .. rubric:: Examples >>> measurement = Measurement(device) >>> params = measurement.test_parameters >>> print(params.powerline_frequency) PowerlineFrequency.FREQ_60_HZ .. py:method:: 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). :param compensation: Compensation data for error correction across all frequencies. :type compensation: Compensation :param measurement_callback: Optional callback invoked after each frequency point with the raw multidevice SMUMeasurement data. Useful for progress tracking or raw data logging. :type measurement_callback: Callable or None :returns: Impedance results for each frequency point, containing compensated impedance magnitude, phase, real and imaginary components. :rtype: list[SMUResult] :raises FrequencyError: If frequency sweep characteristics are not configured. :raises SMUParameterError: If device configuration fails at any frequency. :raises CompensationMethodError: If compensation data is incompatible. .. rubric:: 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") .. py:method:: 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. :param compensation_file_path: Optional file path for the compensation data. If None, auto-generates path using leader serial number. :type compensation_file_path: str or None :param kit_file_path: Optional path to known impedance table for KIT compensation subtraction (applied after all measurements). :type kit_file_path: str or None :param comment: Optional comment to embed in the compensation file. :type comment: str or None :param measurement_callback: Optional callback invoked after each frequency measurement with the raw multidevice SMUMeasurement data. :type measurement_callback: Callable or None :returns: Path to the created compensation file. :rtype: str :raises CompensationMethodError: If NO_COMPENSATION is selected or method is unsupported. :raises FrequencyError: If frequency sweep characteristics are not configured. :raises SMUParameterError: If device configuration or measurement fails. .. py:method:: run_with_switching(compensation=None, measurement_callback=None) :abstractmethod: 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. .. py:attribute:: current_frequency :value: 1000.0 Get the current frequency of the ACIR Measurement. :returns: The current configured frequency in Hz :rtype: float :raises FrequencyError: If frequency has not been set .. py:property:: all_measurement_data :type: 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. :rtype: Iterator[tuple[Device, SMUMeasurement | None]] .. rubric:: Example >>> for device, measurement in parallel_acir.all_measurement_data: ... print(f"Device: {device.product}") ... print(f"Voltage samples: {len(measurement.voltage_values)}") .. py:property:: calibration_file_paths :type: 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. :rtype: list[tuple[Device, str]] .. py:property:: measurement_data :type: 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. :rtype: SMUMeasurement .. py:method:: 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. :rtype: str :raises CompensationMethodError: If compensation_method is not supported. .. py:method:: 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. .. py:property:: result :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] 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 .. rubric:: 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") .. py:attribute:: DEVICE_FAMILY :type: nibcq.enums.DeviceFamily :value: None Device family for ACIR and similar measurement types. :type: DeviceFamily .. py:property:: acceptable_temperature_delta :type: 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 :rtype: float .. rubric:: Examples >>> measurement = EIS(device) >>> measurement.acceptable_temperature_delta = 2.5 >>> delta = measurement.acceptable_temperature_delta .. py:property:: temperature :type: float Get the latest temperature reading from the device. :returns: The most recent temperature measurement, or NaN if no temperature capability :rtype: float .. py:property:: temperature_range :type: 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). .. py:method:: measure_temperature() -> CenteredRange Get a new temperature reading from the device. :returns: Current temperature reading, or NaN if no temperature capability .. py:method:: 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. :param target_temperature: The target temperature parameters for validation :type target_temperature: CenteredRange :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. :rtype: bool :raises TemperatureError: If the current temperature exceeds the target ± delta range (only raised when capability is configured) .. rubric:: Examples >>> measurement = EIS(device) >>> measurement.measure_temperature() >>> target = compensation.temperature_parameter >>> is_valid = measurement.validate_temperature(target) .. py:attribute:: FREQUENCY_LIMIT :type: Final[float] :value: 10000.0 Frequency limit for ACIR and similar measurement types. :type: float .. py:attribute:: SENSITIVITY_FREQUENCY_LIMIT :type: Final[float] :value: 7500.0 Frequency limit for DEVICE_CURRENT_LIMIT A Current measurements. :type: float .. py:attribute:: CURRENT_LIMIT :type: Final[float] :value: 2.0 High Frequency current limit for ACIR and similar measurement types. :type: float .. py:attribute:: DEVICE_CURRENT_LIMIT :type: Final[float] :value: 3.0 Moderate Frequency current limit for ACIR and similar measurement types. :type: float .. py:property:: raw_data :type: 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 :rtype: SMUMeasurement :raises SMUParameterError: If no measurement data is available or measurement is incomplete .. py:method:: validate_current_amplitude(target_frequency: float, current_amplitude: float) -> bool :staticmethod: Validate that the current amplitude is within acceptable limits. :param target_frequency: The target frequency in Hz. Determines which current limit applies (CURRENT_LIMIT above SENSITIVITY_FREQUENCY_LIMIT, DEVICE_CURRENT_LIMIT at or below). :type target_frequency: float :param current_amplitude: The current amplitude to validate (in Amps). :type current_amplitude: float :returns: Always returns True when validation passes. :rtype: bool :raises CurrentAmplitudeError: If the current amplitude is not positive or exceeds the defined maximum limit (defined in ACIR.CURRENT_LIMIT constant). .. py:method:: load_compensation_file(file_path: Optional[str] = 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. :param file_path: Optional specific file path to read from. If None, generates path automatically :type file_path: str, optional :returns: The loaded compensation object :rtype: Compensation :raises FileNotFoundError: If compensation file is not found and compensation is required :raises ValueError: If compensation file is invalid and compensation is required :raises CompensationMethodError: If compensation method is not supported by the nibcq Python API .. py:property:: has_switch_capability :type: bool Check if the device has switch capability. :returns: True if switch capability is available, False otherwise .. py:method:: connect_channel(channel: SMUCellData) -> None Connect to a specific DUT channel using the device's switch capability. :param channel: SwitchChannel containing connection information :type channel: SMUCellData :raises RuntimeError: If no switch capability is available .. py:method:: disconnect_all() -> None Disconnect all channels using the device's switch capability. :raises RuntimeError: If no switch capability is available .. py:method:: wait_for_debounce() -> None Wait for switch relays to settle using the device's switch capability. :raises RuntimeError: If no switch capability is available .. py:property:: switch_cells :type: 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. :rtype: list[str] | list[SMUCellData] .. py: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 :rtype: list[float] :raises FrequencyError: If frequency sweep characteristics are not set or empty .. rubric:: Examples >>> eis.frequency_list [1000.0, 500.0, 100.0, 50.0, 10.0] .. py:method:: 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.