Spaces:
Running
Running
| """ | |
| Cylinder simulation service. | |
| Calculates hydraulic cylinder performance based on geometry and operating parameters. | |
| """ | |
| import math | |
| from typing import Optional | |
| from dataclasses import dataclass | |
| class SimulationInput: | |
| """Input parameters for cylinder simulation.""" | |
| # Geometry (required) | |
| bore_diameter_mm: float | |
| rod_diameter_mm: float | |
| stroke_mm: float | |
| # Operating conditions | |
| supply_pressure_mpa: float = 21.0 # Typical excavator hydraulic pressure | |
| return_pressure_mpa: float = 0.5 # Back pressure | |
| flow_rate_lpm: float = 100.0 # Liters per minute | |
| # Load conditions | |
| external_load_kn: float = 0.0 # External load on rod | |
| # Efficiency factors | |
| mechanical_efficiency: float = 0.95 | |
| volumetric_efficiency: float = 0.98 | |
| # Pressure drop parameters (valve + line losses) | |
| valve_pressure_drop_mpa: float = 0.8 # Directional valve pressure drop | |
| line_pressure_drop_mpa: float = 0.4 # Hose/fitting pressure losses | |
| # FIX 15: Pump dynamics parameters | |
| pump_type: str = "fixed" # "fixed" or "variable" displacement | |
| pump_max_pressure_mpa: float = 25.0 # Max pump pressure before relief | |
| relief_valve_cracking_mpa: float = 23.0 # Relief valve cracking pressure | |
| relief_valve_full_flow_mpa: float = 25.0 # Relief valve full flow pressure | |
| pump_speed_rpm: float = 1500.0 # Pump rotational speed (for ripple calc) | |
| pump_pistons: int = 9 # Number of pump pistons (for ripple) | |
| variable_pump_cutoff_mpa: float = 20.0 # Pressure where variable pump destokes | |
| class SimulationResult: | |
| """Calculated results from cylinder simulation.""" | |
| # Areas (mm²) | |
| piston_area_mm2: float | |
| rod_area_mm2: float | |
| annulus_area_mm2: float | |
| area_ratio: float | |
| # Forces (kN) | |
| max_push_force_kn: float | |
| max_pull_force_kn: float | |
| effective_push_force_kn: float | |
| effective_pull_force_kn: float | |
| # Speeds (m/s) | |
| extend_speed_m_s: float | |
| retract_speed_m_s: float | |
| # Volumes (liters) | |
| extend_volume_liters: float | |
| retract_volume_liters: float | |
| # Cycle times (seconds) | |
| extend_time_s: float | |
| retract_time_s: float | |
| cycle_time_s: float | |
| cycles_per_minute: float | |
| # Power (kW) | |
| extend_power_kw: float | |
| retract_power_kw: float | |
| # Additional info | |
| pressure_to_overcome_load_mpa: Optional[float] = None | |
| speed_under_load_m_s: Optional[float] = None | |
| # Pressure drop info (for transparency) | |
| total_pressure_drop_mpa: float = 0.0 | |
| effective_supply_pressure_mpa: float = 0.0 | |
| # FIX 15: Pump dynamics outputs | |
| pump_flow_factor: float = 1.0 # Flow reduction due to pump curve | |
| relief_valve_flow_lpm: float = 0.0 # Flow through relief valve | |
| pump_ripple_amplitude_percent: float = 0.0 # Pressure ripple amplitude | |
| pump_ripple_frequency_hz: float = 0.0 # Pressure ripple frequency | |
| def run_simulation(params: SimulationInput) -> SimulationResult: | |
| """ | |
| Run cylinder performance simulation. | |
| Calculates forces, speeds, volumes, and cycle times based on | |
| cylinder geometry and operating conditions. | |
| """ | |
| # Calculate areas | |
| piston_area_mm2 = math.pi * (params.bore_diameter_mm / 2) ** 2 | |
| rod_area_mm2 = math.pi * (params.rod_diameter_mm / 2) ** 2 | |
| annulus_area_mm2 = piston_area_mm2 - rod_area_mm2 | |
| area_ratio = piston_area_mm2 / annulus_area_mm2 if annulus_area_mm2 > 0 else 0 | |
| # Convert areas to m² for force calculations | |
| piston_area_m2 = piston_area_mm2 / 1_000_000 | |
| annulus_area_m2 = annulus_area_mm2 / 1_000_000 | |
| # Total pressure drop (valve + line losses) | |
| # Real systems have 0.5-2.0 MPa drop across valves, fittings, and hoses | |
| total_pressure_drop_mpa = params.valve_pressure_drop_mpa + params.line_pressure_drop_mpa | |
| # Effective supply pressure at cylinder (after losses) | |
| effective_supply_pressure_mpa = params.supply_pressure_mpa - total_pressure_drop_mpa | |
| # Net pressure (effective supply - back pressure) | |
| net_pressure_mpa = effective_supply_pressure_mpa - params.return_pressure_mpa | |
| # Force calculations (F = P × A) | |
| # Pressure in MPa = N/mm², Area in mm² → Force in N → /1000 = kN | |
| # Correct formula accounts for pressure acting on BOTH chambers: | |
| # F_extend = P_supply × A_piston - P_return × A_annulus | |
| # F_retract = P_supply × A_annulus - P_return × A_piston | |
| # Return pressure acts on the OPPOSITE chamber, not the same side | |
| max_push_force_kn = (effective_supply_pressure_mpa * piston_area_mm2 - | |
| params.return_pressure_mpa * annulus_area_mm2) / 1000 | |
| max_pull_force_kn = (effective_supply_pressure_mpa * annulus_area_mm2 - | |
| params.return_pressure_mpa * piston_area_mm2) / 1000 | |
| # Apply mechanical efficiency | |
| effective_push_force_kn = max_push_force_kn * params.mechanical_efficiency | |
| effective_pull_force_kn = max_pull_force_kn * params.mechanical_efficiency | |
| # Speed calculations (v = Q / A) | |
| # Flow in LPM → m³/s: LPM / 60000 | |
| # Area in mm² → m²: mm² / 1_000_000 | |
| flow_m3_s = (params.flow_rate_lpm / 60000) * params.volumetric_efficiency | |
| extend_speed_m_s = flow_m3_s / piston_area_m2 if piston_area_m2 > 0 else 0 | |
| retract_speed_m_s = flow_m3_s / annulus_area_m2 if annulus_area_m2 > 0 else 0 | |
| # Volume calculations | |
| stroke_m = params.stroke_mm / 1000 | |
| extend_volume_m3 = piston_area_m2 * stroke_m | |
| retract_volume_m3 = annulus_area_m2 * stroke_m | |
| extend_volume_liters = extend_volume_m3 * 1000 | |
| retract_volume_liters = retract_volume_m3 * 1000 | |
| # Cycle time calculations | |
| extend_time_s = stroke_m / extend_speed_m_s if extend_speed_m_s > 0 else 0 | |
| retract_time_s = stroke_m / retract_speed_m_s if retract_speed_m_s > 0 else 0 | |
| cycle_time_s = extend_time_s + retract_time_s | |
| cycles_per_minute = 60 / cycle_time_s if cycle_time_s > 0 else 0 | |
| # Power calculations (P = F × v) | |
| extend_power_kw = effective_push_force_kn * extend_speed_m_s | |
| retract_power_kw = effective_pull_force_kn * retract_speed_m_s | |
| # Load analysis | |
| pressure_to_overcome_load_mpa = None | |
| speed_under_load_m_s = None | |
| # FIX 15: Initialize pump dynamics variables | |
| pump_flow_factor = 1.0 | |
| relief_valve_flow_lpm = 0.0 | |
| pump_ripple_frequency_hz = params.pump_speed_rpm * params.pump_pistons / 60.0 | |
| pump_ripple_amplitude_percent = 2.0 if params.pump_type == "fixed" else 1.5 | |
| if params.external_load_kn > 0: | |
| # Pressure needed to overcome external load during extension | |
| pressure_to_overcome_load_mpa = (params.external_load_kn * 1000) / piston_area_mm2 | |
| # Available force after overcoming load | |
| available_force_kn = effective_push_force_kn - params.external_load_kn | |
| if available_force_kn > 0: | |
| # Speed reduction under load due to: | |
| # 1. Internal leakage increases with higher working pressure | |
| # 2. Pump flow curve - flow drops as pressure rises (typically 10-15% at max pressure) | |
| # 3. Valve pressure drop increases with flow squared | |
| # Calculate working pressure ratio (higher load = higher pressure) | |
| working_pressure_mpa = pressure_to_overcome_load_mpa + 1.0 # Add margin | |
| pressure_ratio = working_pressure_mpa / params.supply_pressure_mpa | |
| pressure_ratio = min(1.0, pressure_ratio) # Cap at 100% | |
| # Internal leakage increases with pressure differential | |
| # Leakage flow: Q_leak = k * ΔP (approximately linear with pressure) | |
| leakage_factor = 1.0 - (params.leakage_coefficient if hasattr(params, 'leakage_coefficient') else 0.005) * pressure_ratio * 10 | |
| # ===== FIX 15: ENHANCED PUMP DYNAMICS ===== | |
| # 1. Pump flow curve depends on pump type | |
| if params.pump_type == "variable": | |
| # Variable displacement pump: destroke (reduce displacement) as pressure rises | |
| # Flow drops sharply after cutoff pressure (pressure compensated) | |
| if working_pressure_mpa < params.variable_pump_cutoff_mpa: | |
| # Below cutoff: full flow with minimal losses | |
| pump_flow_factor = 1.0 - 0.03 * pressure_ratio # 3% loss at max | |
| else: | |
| # Above cutoff: rapid destroke - flow proportional to (1 - (P-Pcutoff)/(Pmax-Pcutoff)) | |
| destroke_ratio = (working_pressure_mpa - params.variable_pump_cutoff_mpa) / \ | |
| max(0.1, params.pump_max_pressure_mpa - params.variable_pump_cutoff_mpa) | |
| destroke_ratio = min(1.0, destroke_ratio) | |
| pump_flow_factor = max(0.05, 1.0 - 0.95 * destroke_ratio) # Min 5% flow | |
| else: | |
| # Fixed displacement pump: flow drops due to internal leakage | |
| # Typical curve: Q = Q_nominal × (1 - k × (P/P_max)²) where k ≈ 0.10-0.15 | |
| pump_flow_factor = 1.0 - 0.12 * pressure_ratio ** 2 | |
| # 2. Relief valve dynamics | |
| # Relief valve opens progressively between cracking and full-flow pressure | |
| # Q_relief = Cv × sqrt(P - P_crack) when P > P_crack | |
| relief_valve_flow_lpm = 0.0 | |
| if working_pressure_mpa > params.relief_valve_cracking_mpa: | |
| # Progressive opening: flow increases with sqrt of pressure above cracking | |
| pressure_above_crack = working_pressure_mpa - params.relief_valve_cracking_mpa | |
| full_flow_pressure_diff = params.relief_valve_full_flow_mpa - params.relief_valve_cracking_mpa | |
| if full_flow_pressure_diff > 0: | |
| opening_ratio = min(1.0, math.sqrt(pressure_above_crack / full_flow_pressure_diff)) | |
| # At full opening, relief can pass full pump flow | |
| relief_valve_flow_lpm = params.flow_rate_lpm * opening_ratio | |
| # Reduce effective pump flow by relief amount | |
| pump_flow_factor *= max(0.1, 1.0 - opening_ratio * 0.9) | |
| # 3. Pump ripple (pressure pulsation) | |
| # Frequency = pump_speed × number_of_pistons / 60 | |
| # Amplitude depends on pump type and pressure (typically 2-5% of working pressure) | |
| pump_ripple_frequency_hz = params.pump_speed_rpm * params.pump_pistons / 60.0 | |
| # Ripple amplitude as percentage of working pressure | |
| if params.pump_type == "variable": | |
| # Variable pumps have lower ripple due to swashplate damping | |
| pump_ripple_amplitude_percent = 1.5 + 1.0 * pressure_ratio # 1.5-2.5% | |
| else: | |
| # Fixed displacement pumps have higher ripple | |
| pump_ripple_amplitude_percent = 2.5 + 2.0 * pressure_ratio # 2.5-4.5% | |
| # Combined speed reduction | |
| speed_reduction_factor = leakage_factor * pump_flow_factor * params.volumetric_efficiency | |
| speed_reduction_factor = max(0.1, min(1.0, speed_reduction_factor)) # Limit reduction | |
| speed_under_load_m_s = extend_speed_m_s * speed_reduction_factor | |
| else: | |
| speed_under_load_m_s = 0.0 | |
| return SimulationResult( | |
| piston_area_mm2=round(piston_area_mm2, 2), | |
| rod_area_mm2=round(rod_area_mm2, 2), | |
| annulus_area_mm2=round(annulus_area_mm2, 2), | |
| area_ratio=round(area_ratio, 3), | |
| max_push_force_kn=round(max_push_force_kn, 2), | |
| max_pull_force_kn=round(max_pull_force_kn, 2), | |
| effective_push_force_kn=round(effective_push_force_kn, 2), | |
| effective_pull_force_kn=round(effective_pull_force_kn, 2), | |
| extend_speed_m_s=round(extend_speed_m_s, 4), | |
| retract_speed_m_s=round(retract_speed_m_s, 4), | |
| extend_volume_liters=round(extend_volume_liters, 3), | |
| retract_volume_liters=round(retract_volume_liters, 3), | |
| extend_time_s=round(extend_time_s, 2), | |
| retract_time_s=round(retract_time_s, 2), | |
| cycle_time_s=round(cycle_time_s, 2), | |
| cycles_per_minute=round(cycles_per_minute, 2), | |
| extend_power_kw=round(extend_power_kw, 2), | |
| retract_power_kw=round(retract_power_kw, 2), | |
| pressure_to_overcome_load_mpa=round(pressure_to_overcome_load_mpa, 2) if pressure_to_overcome_load_mpa else None, | |
| speed_under_load_m_s=round(speed_under_load_m_s, 4) if speed_under_load_m_s is not None else None, | |
| total_pressure_drop_mpa=round(total_pressure_drop_mpa, 2), | |
| effective_supply_pressure_mpa=round(effective_supply_pressure_mpa, 2), | |
| # FIX 15: Pump dynamics outputs | |
| pump_flow_factor=round(pump_flow_factor, 3), | |
| relief_valve_flow_lpm=round(relief_valve_flow_lpm, 2), | |
| pump_ripple_amplitude_percent=round(pump_ripple_amplitude_percent, 2), | |
| pump_ripple_frequency_hz=round(pump_ripple_frequency_hz, 1), | |
| ) | |