Implement physics engine stepping
Full implementation of step() method with thermal-electrical coupling: - Chamber temperature first-order response to setpoint - Case temperature with self-heating via thermal calculations - Junction temperature from θ_jc thermal resistance - Electrical state from temperature-dependent DUT model - Default LDO model when none provided
This commit is contained in:
@@ -10,6 +10,11 @@ from __future__ import annotations
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from py_dvt_ate.simulation.physics.state import ElectricalState, ThermalState
|
from py_dvt_ate.simulation.physics.state import ElectricalState, ThermalState
|
||||||
|
from py_dvt_ate.simulation.physics.thermal import (
|
||||||
|
calculate_junction_temperature,
|
||||||
|
update_case_temperature,
|
||||||
|
update_temperature,
|
||||||
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from py_dvt_ate.simulation.physics.models.base import DUTModel
|
from py_dvt_ate.simulation.physics.models.base import DUTModel
|
||||||
@@ -21,6 +26,12 @@ class PhysicsEngine:
|
|||||||
Runs at a fixed timestep, updating thermal and electrical state
|
Runs at a fixed timestep, updating thermal and electrical state
|
||||||
based on the DUT model and environmental conditions.
|
based on the DUT model and environmental conditions.
|
||||||
|
|
||||||
|
The simulation models:
|
||||||
|
- Chamber temperature approaching setpoint (first-order response)
|
||||||
|
- Case temperature driven by chamber and self-heating
|
||||||
|
- Junction temperature from case temperature and thermal resistance
|
||||||
|
- Electrical behaviour from the DUT model (temperature-dependent)
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
dt: Simulation timestep in seconds.
|
dt: Simulation timestep in seconds.
|
||||||
"""
|
"""
|
||||||
@@ -35,12 +46,19 @@ class PhysicsEngine:
|
|||||||
Args:
|
Args:
|
||||||
update_rate_hz: Simulation update rate in Hz. Defaults to 100.
|
update_rate_hz: Simulation update rate in Hz. Defaults to 100.
|
||||||
dut_model: DUT model to use for electrical calculations.
|
dut_model: DUT model to use for electrical calculations.
|
||||||
If None, a default model will be used when implemented.
|
If None, a default LDO model will be used.
|
||||||
"""
|
"""
|
||||||
self.dt = 1.0 / update_rate_hz
|
self.dt = 1.0 / update_rate_hz
|
||||||
|
|
||||||
|
# Lazily import to avoid circular dependencies
|
||||||
|
if dut_model is None:
|
||||||
|
from py_dvt_ate.simulation.physics.models.ldo import LDOModel
|
||||||
|
|
||||||
|
self._dut: DUTModel = LDOModel()
|
||||||
|
else:
|
||||||
self._dut = dut_model
|
self._dut = dut_model
|
||||||
|
|
||||||
# Thermal parameters (to be configured in Sprint 3)
|
# Thermal parameters
|
||||||
self._tau_chamber = 30.0 # seconds
|
self._tau_chamber = 30.0 # seconds
|
||||||
self._tau_case = 5.0 # seconds
|
self._tau_case = 5.0 # seconds
|
||||||
self._theta_jc = 15.0 # degC/W
|
self._theta_jc = 15.0 # degC/W
|
||||||
@@ -59,9 +77,34 @@ class PhysicsEngine:
|
|||||||
"""Advance simulation by one timestep.
|
"""Advance simulation by one timestep.
|
||||||
|
|
||||||
Updates thermal and electrical state based on current conditions.
|
Updates thermal and electrical state based on current conditions.
|
||||||
Full implementation in Sprint 3.
|
The thermal-electrical coupling works as follows:
|
||||||
|
1. Calculate current power dissipation from DUT model
|
||||||
|
2. Update chamber temperature towards setpoint
|
||||||
|
3. Update case temperature including self-heating
|
||||||
|
4. Advance simulation time
|
||||||
"""
|
"""
|
||||||
# Stub: just advance time
|
# Calculate power dissipation (uses current junction temperature estimate)
|
||||||
|
p_diss = self._calculate_power_dissipation()
|
||||||
|
|
||||||
|
# Update chamber temperature (first-order response to setpoint)
|
||||||
|
self._t_chamber = update_temperature(
|
||||||
|
current_temperature=self._t_chamber,
|
||||||
|
target_temperature=self._t_setpoint,
|
||||||
|
time_constant=self._tau_chamber,
|
||||||
|
dt=self.dt,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Update case temperature (driven by chamber + self-heating)
|
||||||
|
self._t_case = update_case_temperature(
|
||||||
|
case_temperature=self._t_case,
|
||||||
|
ambient_temperature=self._t_chamber,
|
||||||
|
power_dissipation=p_diss,
|
||||||
|
time_constant=self._tau_case,
|
||||||
|
theta_ca=self._theta_ca,
|
||||||
|
dt=self.dt,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Advance simulation time
|
||||||
self._sim_time += self.dt
|
self._sim_time += self.dt
|
||||||
|
|
||||||
def get_thermal_state(self) -> ThermalState:
|
def get_thermal_state(self) -> ThermalState:
|
||||||
@@ -70,11 +113,17 @@ class PhysicsEngine:
|
|||||||
Returns:
|
Returns:
|
||||||
Immutable ThermalState with current temperatures.
|
Immutable ThermalState with current temperatures.
|
||||||
"""
|
"""
|
||||||
# Stub: return current state values
|
p_diss = self._calculate_power_dissipation()
|
||||||
|
t_junction = calculate_junction_temperature(
|
||||||
|
case_temperature=self._t_case,
|
||||||
|
power_dissipation=p_diss,
|
||||||
|
theta_jc=self._theta_jc,
|
||||||
|
)
|
||||||
|
|
||||||
return ThermalState(
|
return ThermalState(
|
||||||
chamber_temperature=self._t_chamber,
|
chamber_temperature=self._t_chamber,
|
||||||
case_temperature=self._t_case,
|
case_temperature=self._t_case,
|
||||||
junction_temperature=self._t_case, # Stub: junction = case
|
junction_temperature=t_junction,
|
||||||
timestamp=self._sim_time,
|
timestamp=self._sim_time,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -84,13 +133,50 @@ class PhysicsEngine:
|
|||||||
Returns:
|
Returns:
|
||||||
Immutable ElectricalState with current electrical values.
|
Immutable ElectricalState with current electrical values.
|
||||||
"""
|
"""
|
||||||
# Stub: return placeholder values
|
p_diss = self._calculate_power_dissipation()
|
||||||
|
t_junction = calculate_junction_temperature(
|
||||||
|
case_temperature=self._t_case,
|
||||||
|
power_dissipation=p_diss,
|
||||||
|
theta_jc=self._theta_jc,
|
||||||
|
)
|
||||||
|
|
||||||
|
if self._output_enabled:
|
||||||
|
v_out = self._dut.calculate_output_voltage(t_junction)
|
||||||
|
i_q = self._dut.calculate_quiescent_current(t_junction)
|
||||||
|
i_load = self._i_load
|
||||||
|
else:
|
||||||
|
v_out = 0.0
|
||||||
|
i_q = 0.0
|
||||||
|
i_load = 0.0
|
||||||
|
|
||||||
return ElectricalState(
|
return ElectricalState(
|
||||||
input_voltage=self._v_in,
|
input_voltage=self._v_in,
|
||||||
output_voltage=0.0, # Stub: will be calculated from DUT model
|
output_voltage=v_out,
|
||||||
load_current=self._i_load if self._output_enabled else 0.0,
|
load_current=i_load,
|
||||||
quiescent_current=0.0, # Stub: will be calculated from DUT model
|
quiescent_current=i_q,
|
||||||
power_dissipation=0.0, # Stub: will be calculated from DUT model
|
power_dissipation=p_diss,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _calculate_power_dissipation(self) -> float:
|
||||||
|
"""Calculate current power dissipation.
|
||||||
|
|
||||||
|
Uses the current case temperature as an approximation for junction
|
||||||
|
temperature in the power calculation. The true junction temperature
|
||||||
|
depends on power dissipation, creating a feedback loop that is
|
||||||
|
resolved iteratively through the simulation steps.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Power dissipation in watts.
|
||||||
|
"""
|
||||||
|
if not self._output_enabled:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
# Use case temperature as junction estimate for power calculation
|
||||||
|
# This avoids circular dependency in the calculation
|
||||||
|
return self._dut.calculate_power_dissipation(
|
||||||
|
input_voltage=self._v_in,
|
||||||
|
load_current=self._i_load,
|
||||||
|
junction_temperature=self._t_case,
|
||||||
)
|
)
|
||||||
|
|
||||||
def set_chamber_setpoint(self, temperature: float) -> None:
|
def set_chamber_setpoint(self, temperature: float) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user