import numpy as np
from piel.types import ScalarMetric, SignalDCCollection
from ..utils import (
get_trace_values_by_datum,
) # Ensure this utility is correctly implemented
from typing import Literal
[docs]
def get_out_min_max(
collection: SignalDCCollection,
lower_threshold_ratio: float = 0.1,
upper_threshold_ratio: float = 0.9,
**kwargs,
) -> ScalarMetric:
"""
Retrieves the minimum and maximum output voltage values within a specified input voltage range.
Args:
collection (SignalDCCollection): The collection of input and output DC signals.
lower_threshold_ratio (float, optional): The lower threshold as a fraction of V_in range (0-1). Defaults to 0.1.
upper_threshold_ratio (float, optional): The upper threshold as a fraction of V_in range (0-1). Defaults to 0.9.
Returns:
ScalarMetric: Metrics including min and max values of the output voltage in the specified input voltage range.
"""
# Validate threshold ratios
if not (0 <= lower_threshold_ratio < upper_threshold_ratio <= 1):
raise ValueError("Threshold ratios must satisfy 0 <= lower < upper <= 1.")
# Identify input voltage trace based on unit datum
input_voltage = None
for signal_dc in collection.inputs:
input_voltage = get_trace_values_by_datum(signal_dc, "voltage")
if input_voltage is not None:
break
if input_voltage is None or len(input_voltage) == 0:
raise ValueError("Input voltage trace not found or empty.")
# Identify output voltage trace based on unit datum
output_voltage = None
output_signal_dc = None
for signal_dc in collection.outputs:
output_voltage = get_trace_values_by_datum(signal_dc, "voltage")
if output_voltage is not None:
output_signal_dc = signal_dc
break
if output_voltage is None or len(output_voltage) == 0:
raise ValueError("Output voltage trace not found or empty.")
if len(input_voltage) != len(output_voltage):
raise ValueError("Input and Output voltage arrays must be of the same length.")
# Define specified thresholds based on input voltage range
V_in_min = np.min(input_voltage)
V_in_max = np.max(input_voltage)
lower_threshold = V_in_min + lower_threshold_ratio * (V_in_max - V_in_min)
upper_threshold = V_in_min + upper_threshold_ratio * (V_in_max - V_in_min)
# Select indices within the specified input voltage range
linear_region_mask = (input_voltage >= lower_threshold) & (
input_voltage <= upper_threshold
)
linear_output_voltages = output_voltage[linear_region_mask]
if len(linear_output_voltages) == 0:
raise ValueError(
"No output voltages found within the specified input voltage range."
)
# Identify the unit for output voltage
output_unit = None
for trace in output_signal_dc.trace_list:
if trace.unit.datum.lower() == "voltage":
output_unit = trace.unit
break
if output_unit is None:
raise ValueError("Output voltage unit not found.")
# Compute min and max output voltages in the linear region
metrics = ScalarMetric(
value=None, # Not applicable
mean=None, # Not applicable
min=np.min(linear_output_voltages),
max=np.max(linear_output_voltages),
standard_deviation=None, # Not applicable
count=None, # Not applicable
unit=output_unit,
)
return metrics
[docs]
def get_out_response_in_transition_range(
collection: "SignalDCCollection",
lower_threshold_ratio: float = 0.1,
upper_threshold_ratio: float = 0.9,
transition_type: Literal["analogue", "digital"] = "analogue",
transition_direction: Literal["positive", "negative"] = "positive",
**kwargs,
) -> "ScalarMetric":
"""
Calculates the equivalent input voltage range (V_in) corresponding to specified thresholds of output voltage (V_out).
Args:
collection (SignalDCCollection): The collection of input and output DC signals.
lower_threshold_ratio (float, optional): The lower threshold as a fraction of V_out's final value (0-1). Defaults to 0.1.
upper_threshold_ratio (float, optional): The upper threshold as a fraction of V_out's final value (0-1). Defaults to 0.9.
transition_type (Literal["analogue", "digital"], optional): Type of transition. Defaults to "analogue".
transition_direction (Literal["positive", "negative"], optional): Direction of transition. Defaults to "positive".
**kwargs: Additional keyword arguments.
Returns:
ScalarMetric: Metrics including min and max V_in values corresponding to the specified V_out threshold range.
"""
# Validate threshold ratios
if not (0 <= lower_threshold_ratio < upper_threshold_ratio <= 1):
raise ValueError("Threshold ratios must satisfy 0 <= lower < upper <= 1.")
# Identify output voltage trace based on unit datum
output_voltage = None
for signal_dc in collection.outputs:
output_voltage = get_trace_values_by_datum(signal_dc, "voltage")
if output_voltage is not None:
break
if output_voltage is None or len(output_voltage) == 0:
raise ValueError("Output voltage trace not found or empty.")
# Identify input voltage trace based on unit datum
input_voltage = None
input_signal_dc = None
for signal_dc in collection.inputs:
input_voltage = get_trace_values_by_datum(signal_dc, "voltage")
if input_voltage is not None:
input_signal_dc = signal_dc
break
if input_voltage is None or len(input_voltage) == 0:
raise ValueError("Input voltage trace not found or empty.")
if len(input_voltage) != len(output_voltage):
raise ValueError("Input and Output voltage arrays must be of the same length.")
# Define specified thresholds based on output voltage's final value
V_out_final = np.max(output_voltage) # Assuming V_out approaches a final value
lower_threshold = lower_threshold_ratio * V_out_final
upper_threshold = upper_threshold_ratio * V_out_final
# Depending on transition direction, adjust the threshold comparison
selected_indices = (output_voltage >= lower_threshold) & (
output_voltage <= upper_threshold
)
corresponding_input_voltages = input_voltage[selected_indices]
if len(corresponding_input_voltages) == 0:
raise ValueError(
"No input voltages found corresponding to the specified output voltage threshold range."
f" Input_voltage: {input_voltage}, Output_voltage: {output_voltage}, "
f"Selected_indices: {selected_indices}, Collection: {collection}"
)
# Identify the unit for input voltage
input_unit = None
for trace in input_signal_dc.trace_list:
if trace.unit.datum.lower() == "voltage":
input_unit = trace.unit
break
if input_unit is None:
raise ValueError("Input voltage unit not found.")
# Calculate metrics based on transition type
if transition_type == "analogue":
# Compute min and max input voltages in the corresponding range
metrics = ScalarMetric(
value=None, # Not applicable
mean=None, # Not applicable
min=np.min(corresponding_input_voltages),
max=np.max(corresponding_input_voltages),
standard_deviation=None, # Not applicable
count=None, # Not applicable
unit=input_unit,
)
elif transition_type == "digital":
# For digital transitions, assuming binary states, find unique states within the range
unique_voltages = np.unique(corresponding_input_voltages)
if len(unique_voltages) < 2:
raise ValueError(
"Not enough unique input voltage levels found for a digital transition."
f" Unique_voltages: {unique_voltages}"
)
elif len(unique_voltages) > 2:
# If more than two unique levels, determine the two closest to the transition thresholds
# Sort unique voltages based on proximity to V_out_final
if transition_direction == "positive":
unique_voltages_sorted = np.sort(unique_voltages)
else:
unique_voltages_sorted = np.sort(unique_voltages)[::-1]
# Take the two extreme values as the digital levels
digital_low = unique_voltages_sorted[0]
digital_high = unique_voltages_sorted[1]
else:
digital_low, digital_high = unique_voltages
metrics = ScalarMetric(
value=None, # Not applicable
mean=None, # Not applicable
min=digital_low,
max=digital_high,
standard_deviation=None, # Not applicable
count=None, # Not applicable
unit=input_unit,
)
else:
raise ValueError("transition_type must be either 'analogue' or 'digital'.")
return metrics