initial commit
This commit is contained in:
257
laser_control/validators.py
Normal file
257
laser_control/validators.py
Normal file
@ -0,0 +1,257 @@
|
||||
"""
|
||||
Parameter validation for laser control module.
|
||||
|
||||
Validates all input parameters against physical constraints
|
||||
and protocol limits before sending to device.
|
||||
"""
|
||||
|
||||
import math
|
||||
from typing import Dict, Any, Tuple
|
||||
|
||||
from .constants import (
|
||||
TEMP_MIN_C, TEMP_MAX_C,
|
||||
CURRENT_MIN_MA, CURRENT_MAX_MA,
|
||||
CURRENT_STEP_MIN_MA, CURRENT_STEP_MAX_MA,
|
||||
TEMP_STEP_MIN_C, TEMP_STEP_MAX_C,
|
||||
TIME_STEP_MIN_US, TIME_STEP_MAX_US,
|
||||
DELAY_TIME_MIN_MS, DELAY_TIME_MAX_MS,
|
||||
)
|
||||
from .exceptions import (
|
||||
ValidationError,
|
||||
TemperatureOutOfRangeError,
|
||||
CurrentOutOfRangeError,
|
||||
InvalidParameterError,
|
||||
)
|
||||
from .models import VariationType
|
||||
|
||||
|
||||
class ParameterValidator:
|
||||
"""Validates all input parameters for the laser controller."""
|
||||
|
||||
@staticmethod
|
||||
def _check_numeric(value: Any, param_name: str) -> float:
|
||||
"""Check that value is a valid finite number. Returns float."""
|
||||
if value is None:
|
||||
raise InvalidParameterError(param_name, "Value must not be None")
|
||||
if not isinstance(value, (int, float)):
|
||||
raise InvalidParameterError(param_name, "Value must be a number")
|
||||
if math.isnan(value):
|
||||
raise InvalidParameterError(param_name, "Value must not be NaN")
|
||||
if math.isinf(value):
|
||||
raise InvalidParameterError(param_name, "Value must not be infinite")
|
||||
return float(value)
|
||||
|
||||
@staticmethod
|
||||
def validate_temperature(value: Any, param_name: str) -> float:
|
||||
"""
|
||||
Validate a laser temperature value.
|
||||
|
||||
Args:
|
||||
value: Temperature in °C.
|
||||
param_name: Parameter name for error messages.
|
||||
|
||||
Returns:
|
||||
Validated temperature as float.
|
||||
|
||||
Raises:
|
||||
InvalidParameterError: If value is not a valid number.
|
||||
TemperatureOutOfRangeError: If value is outside [TEMP_MIN_C, TEMP_MAX_C].
|
||||
"""
|
||||
value = ParameterValidator._check_numeric(value, param_name)
|
||||
if value < TEMP_MIN_C or value > TEMP_MAX_C:
|
||||
raise TemperatureOutOfRangeError(
|
||||
param_name, value, TEMP_MIN_C, TEMP_MAX_C
|
||||
)
|
||||
return value
|
||||
|
||||
@staticmethod
|
||||
def validate_current(value: Any, param_name: str) -> float:
|
||||
"""
|
||||
Validate a laser drive current value.
|
||||
|
||||
Args:
|
||||
value: Current in mA.
|
||||
param_name: Parameter name for error messages.
|
||||
|
||||
Returns:
|
||||
Validated current as float.
|
||||
|
||||
Raises:
|
||||
InvalidParameterError: If value is not a valid number.
|
||||
CurrentOutOfRangeError: If value is outside [CURRENT_MIN_MA, CURRENT_MAX_MA].
|
||||
"""
|
||||
value = ParameterValidator._check_numeric(value, param_name)
|
||||
if value < CURRENT_MIN_MA or value > CURRENT_MAX_MA:
|
||||
raise CurrentOutOfRangeError(
|
||||
param_name, value, CURRENT_MIN_MA, CURRENT_MAX_MA
|
||||
)
|
||||
return value
|
||||
|
||||
@staticmethod
|
||||
def validate_time_params(time_step: Any, delay_time: Any) -> Tuple[int, int]:
|
||||
"""
|
||||
Validate time parameters for variation mode.
|
||||
|
||||
Args:
|
||||
time_step: Discretisation time step in microseconds.
|
||||
delay_time: Delay between pulses in milliseconds.
|
||||
|
||||
Returns:
|
||||
Tuple (time_step, delay_time) as integers.
|
||||
|
||||
Raises:
|
||||
InvalidParameterError: If values are not numeric.
|
||||
ValidationError: If values are outside allowed ranges.
|
||||
"""
|
||||
if not isinstance(time_step, (int, float)):
|
||||
raise InvalidParameterError("time_step", "Value must be a number")
|
||||
if not isinstance(delay_time, (int, float)):
|
||||
raise InvalidParameterError("delay_time", "Value must be a number")
|
||||
|
||||
time_step_int = int(time_step)
|
||||
delay_time_int = int(delay_time)
|
||||
|
||||
if time_step_int < TIME_STEP_MIN_US or time_step_int > TIME_STEP_MAX_US:
|
||||
raise ValidationError(
|
||||
f"time step {time_step_int} µs is out of range "
|
||||
f"[{TIME_STEP_MIN_US} - {TIME_STEP_MAX_US}] µs"
|
||||
)
|
||||
if delay_time_int < DELAY_TIME_MIN_MS or delay_time_int > DELAY_TIME_MAX_MS:
|
||||
raise ValidationError(
|
||||
f"delay time {delay_time_int} ms is out of range "
|
||||
f"[{DELAY_TIME_MIN_MS} - {DELAY_TIME_MAX_MS}] ms"
|
||||
)
|
||||
return time_step_int, delay_time_int
|
||||
|
||||
@staticmethod
|
||||
def validate_variation_params(
|
||||
params: Dict[str, Any],
|
||||
variation_type: Any
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Validate parameters for variation mode.
|
||||
|
||||
Args:
|
||||
params: Dictionary with keys:
|
||||
min_value, max_value, step, time_step, delay_time.
|
||||
variation_type: A VariationType enum value.
|
||||
|
||||
Returns:
|
||||
Dictionary with validated and type-coerced values.
|
||||
|
||||
Raises:
|
||||
ValidationError: For any constraint violation.
|
||||
InvalidParameterError: For wrong types.
|
||||
"""
|
||||
# Validate variation type
|
||||
if not isinstance(variation_type, VariationType):
|
||||
try:
|
||||
variation_type = VariationType(variation_type)
|
||||
except (ValueError, KeyError):
|
||||
raise ValidationError(
|
||||
f"Invalid variation type '{variation_type}'. "
|
||||
f"Must be one of {[e.name for e in VariationType]}"
|
||||
)
|
||||
|
||||
# Check required keys
|
||||
required_keys = {'min_value', 'max_value', 'step', 'time_step', 'delay_time'}
|
||||
missing = required_keys - params.keys()
|
||||
if missing:
|
||||
raise ValidationError(
|
||||
f"Missing required parameters: {sorted(missing)}"
|
||||
)
|
||||
|
||||
# Validate min/max
|
||||
min_val = ParameterValidator._check_numeric(params['min_value'], 'min_value')
|
||||
max_val = ParameterValidator._check_numeric(params['max_value'], 'max_value')
|
||||
|
||||
if min_val >= max_val:
|
||||
raise ValidationError(
|
||||
f"min_value ({min_val}) must be less than max_value ({max_val})"
|
||||
)
|
||||
|
||||
# Validate step based on variation type
|
||||
step = ParameterValidator._check_numeric(params['step'], 'step')
|
||||
|
||||
is_current_variation = variation_type in (
|
||||
VariationType.CHANGE_CURRENT_LD1,
|
||||
VariationType.CHANGE_CURRENT_LD2
|
||||
)
|
||||
is_temp_variation = variation_type in (
|
||||
VariationType.CHANGE_TEMPERATURE_LD1,
|
||||
VariationType.CHANGE_TEMPERATURE_LD2
|
||||
)
|
||||
|
||||
if is_current_variation:
|
||||
step_min, step_max = CURRENT_STEP_MIN_MA, CURRENT_STEP_MAX_MA
|
||||
unit = "mA"
|
||||
# Also validate range against current limits
|
||||
ParameterValidator.validate_current(min_val, 'min_value')
|
||||
ParameterValidator.validate_current(max_val, 'max_value')
|
||||
elif is_temp_variation:
|
||||
step_min, step_max = TEMP_STEP_MIN_C, TEMP_STEP_MAX_C
|
||||
unit = "°C"
|
||||
# Also validate range against temperature limits
|
||||
ParameterValidator.validate_temperature(min_val, 'min_value')
|
||||
ParameterValidator.validate_temperature(max_val, 'max_value')
|
||||
else:
|
||||
raise ValidationError(
|
||||
f"Variation type {variation_type.name} cannot be used in variation mode"
|
||||
)
|
||||
|
||||
if step <= 0:
|
||||
raise ValidationError(
|
||||
f"step must be positive, got {step} {unit}"
|
||||
)
|
||||
if step < step_min:
|
||||
raise ValidationError(
|
||||
f"step {step} {unit} is too small (minimum {step_min} {unit})"
|
||||
)
|
||||
if step > step_max:
|
||||
raise ValidationError(
|
||||
f"step {step} {unit} is too large (maximum {step_max} {unit})"
|
||||
)
|
||||
|
||||
# Validate time parameters
|
||||
time_step, delay_time = ParameterValidator.validate_time_params(
|
||||
params['time_step'], params['delay_time']
|
||||
)
|
||||
|
||||
return {
|
||||
'variation_type': variation_type,
|
||||
'min_value': min_val,
|
||||
'max_value': max_val,
|
||||
'step': step,
|
||||
'time_step': time_step,
|
||||
'delay_time': delay_time,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def validate_manual_mode_params(
|
||||
temp1: Any,
|
||||
temp2: Any,
|
||||
current1: Any,
|
||||
current2: Any,
|
||||
) -> Dict[str, float]:
|
||||
"""
|
||||
Validate all four manual mode parameters.
|
||||
|
||||
Args:
|
||||
temp1: Laser 1 temperature, °C.
|
||||
temp2: Laser 2 temperature, °C.
|
||||
current1: Laser 1 current, mA.
|
||||
current2: Laser 2 current, mA.
|
||||
|
||||
Returns:
|
||||
Dict with validated floats: temp1, temp2, current1, current2.
|
||||
|
||||
Raises:
|
||||
ValidationError: For any out-of-range value.
|
||||
InvalidParameterError: For wrong types.
|
||||
"""
|
||||
return {
|
||||
'temp1': ParameterValidator.validate_temperature(temp1, 'temp1'),
|
||||
'temp2': ParameterValidator.validate_temperature(temp2, 'temp2'),
|
||||
'current1': ParameterValidator.validate_current(current1, 'current1'),
|
||||
'current2': ParameterValidator.validate_current(current2, 'current2'),
|
||||
}
|
||||
Reference in New Issue
Block a user