import numpy as np
from piel.types import ScalarMetric, SignalDCCollection, W, SignalDC, SignalTraceDC, V
from ..utils import (
get_trace_values_by_datum,
)
import logging
logger = logging.getLogger(__name__)
[docs]
def calculate_power_signal_from_collection(
collection: SignalDCCollection,
lower_threshold_ratio: float = 0,
upper_threshold_ratio: float = 1,
**kwargs,
) -> ScalarMetric:
"""
Retrieves the minimum and maximum power values within a specified input voltage range,
along with the corresponding V_in values where these extrema occur.
Args:
collection (SignalDCCollection): The collection of input, output, and power 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
- ScalarMetric containing min and max power.
"""
# Validate threshold ratios
if not (0 <= lower_threshold_ratio < upper_threshold_ratio <= 1):
raise ValueError("Threshold ratios must satisfy 0 <= lower < upper <= 1.")
# Extract or compute power values
power = None
power_signal_dc = None
power_unit = None
power_values = None
for signal_dc in collection.power:
# Attempt to get direct power trace
power = get_trace_values_by_datum(signal_dc, "watt")
if power is not None:
power_values = power
# Identify the unit for power
for trace in signal_dc.trace_list:
if trace.unit.datum.lower() == "watt":
power_unit = trace.unit
break
power_signal_dc = signal_dc
break # Power found, exit loop
# If direct power not found, attempt to compute from voltage and current
voltage = get_trace_values_by_datum(signal_dc, "voltage")
current = get_trace_values_by_datum(signal_dc, "ampere")
if voltage is not None and current is not None:
logger.debug("Multiplying voltage and current values")
power_values = voltage * current
# Assume power unit is derived from voltage and current units
voltage_unit = None
current_unit = None
for trace in signal_dc.trace_list:
if trace.unit.datum.lower() == "voltage":
voltage_unit = trace.unit
elif trace.unit.datum.lower() == "ampere":
current_unit = trace.unit
if voltage_unit is not None and current_unit is not None:
# Example: if voltage is in volts and current in amperes, power is in watts
# Adjust accordingly based on actual unit implementation
# power_unit = voltage_unit * current_unit # TODO implement this
power_unit = W
power_signal_trace = SignalTraceDC(
unit=power_unit,
values=power_values,
)
power_signal_dc = SignalDC(
trace_list=[power_signal_trace],
)
break
try:
logger.debug(f"Voltage values: {voltage}")
logger.debug(f"Current values: {current}")
logger.debug(f"Power values: {power_values}")
except Exception:
pass
if power_values is None or len(power_values) == 0:
raise ValueError("Power trace not found or empty in the collection.")
return power_signal_dc
[docs]
def get_power_metrics(
collection: SignalDCCollection,
lower_threshold_ratio: float = 0,
upper_threshold_ratio: float = 1,
**kwargs,
) -> ScalarMetric:
"""
Retrieves the minimum and maximum power values within a specified input voltage range,
along with the corresponding V_in values where these extrema occur.
Args:
collection (SignalDCCollection): The collection of input, output, and power 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
- ScalarMetric containing min and max power.
"""
# Validate threshold ratios
power_signal_dc = calculate_power_signal_from_collection(
collection, lower_threshold_ratio, upper_threshold_ratio
)
power_values = power_signal_dc.trace_list[0].values
power_unit = power_signal_dc.trace_list[0].unit
# 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.")
if len(input_voltage) != len(power_values):
raise ValueError("Input voltage and Power 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
vin_range_mask = (input_voltage >= lower_threshold) & (
input_voltage <= upper_threshold
)
power_in_range = power_values[vin_range_mask]
if len(power_in_range) == 0:
raise ValueError(
"No power values found within the specified input voltage range."
)
# Compute min and max power in the specified range
min_power = np.min(power_in_range)
max_power = np.max(power_in_range)
mean_power = np.mean(power_in_range)
std_power = np.std(power_in_range)
count_power = len(power_in_range)
# std_power = None
# Identify the unit for power if not already identified
if power_unit is None and power_signal_dc is not None:
for trace in power_signal_dc.trace_list:
if trace.unit.datum.lower() == "watt":
power_unit = trace.unit
break
if power_unit is None:
# Default to watts if unit not found
power_unit = W
# Create ScalarMetric for power
metrics = ScalarMetric(
value=mean_power, # Not applicable
mean=mean_power, # Not applicable
min=min_power,
max=max_power,
standard_deviation=std_power, # Not applicable
count=count_power, # Not applicable
unit=power_unit,
)
return metrics
[docs]
def get_power_map_vin_metrics(
collection: SignalDCCollection,
lower_threshold_ratio: float = 0,
upper_threshold_ratio: float = 1,
**kwargs,
) -> ScalarMetric:
"""
Retrieves the mapped V_IN minimum and maximum power values within a specified input voltage range. Represents
along with the corresponding V_in values where these power extrema occur.
Args:
collection (SignalDCCollection): The collection of input, output, and power 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
- ScalarMetric containing min and max power.
"""
# Validate threshold ratios
power_signal_dc = calculate_power_signal_from_collection(
collection, lower_threshold_ratio, upper_threshold_ratio
)
power_values = power_signal_dc.trace_list[0].values
power_unit = power_signal_dc.trace_list[0].unit
# 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.")
if len(input_voltage) != len(power_values):
raise ValueError("Input voltage and Power 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
vin_range_mask = (input_voltage >= lower_threshold) & (
input_voltage <= upper_threshold
)
power_in_range = power_values[vin_range_mask]
corresponding_vin = input_voltage[vin_range_mask]
if len(power_in_range) == 0:
raise ValueError(
"No power values found within the specified input voltage range."
)
# Compute min and max power in the specified range
min_power = np.min(power_in_range)
max_power = np.max(power_in_range)
# std_power = None
# Find the V_in corresponding to min and max power
min_power_indices = np.where(power_in_range == min_power)[0]
# mean_power_indices = np.where(power_in_range == mean_power)[0]
max_power_indices = np.where(power_in_range == max_power)[0]
# In case of multiple occurrences, take the first one
min_mapping_vin = corresponding_vin[min_power_indices[0]]
# mean_mapping_vin = corresponding_vin[mean_power_indices[0]]
max_mapping_vin = corresponding_vin[max_power_indices[0]]
# vin_count = len(corresponding_vin)
# Identify the unit for power if not already identified
if power_unit is None and power_signal_dc is not None:
for trace in power_signal_dc.trace_list:
if trace.unit.datum.lower() == "watt":
power_unit = trace.unit
break
if power_unit is None:
# Default to watts if unit not found
power_unit = W
# Create ScalarMetric for power
metrics = ScalarMetric(
value=None, # Not applicable
mean=None, # Not applicable
min=min_mapping_vin,
max=max_mapping_vin,
standard_deviation=None, # Not applicable
count=None, # Not applicable
unit=V,
)
return metrics