chore: sync project state and current artifacts
This commit is contained in:
@@ -11,6 +11,10 @@ repository.workspace = true
|
||||
name = "entropyk"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[features]
|
||||
default = ["coolprop"]
|
||||
coolprop = ["entropyk-fluids/coolprop"]
|
||||
|
||||
[dependencies]
|
||||
entropyk = { path = "../../crates/entropyk" }
|
||||
entropyk-core = { path = "../../crates/core" }
|
||||
|
||||
@@ -113,19 +113,33 @@ All types support: `__repr__`, `__str__`, `__float__`, `__eq__`, `__add__`, `__s
|
||||
| `FlowSource` | `(pressure_pa, temperature_k)` | Boundary source |
|
||||
| `FlowSink` | `()` | Boundary sink |
|
||||
|
||||
### Solver
|
||||
### Solver Configurations
|
||||
|
||||
| Config | Constructor | Description |
|
||||
|-----------|------------|-------------|
|
||||
| `NewtonConfig` | `(max_iterations, tolerance, line_search, timeout_ms, initial_state, use_numerical_jacobian, jacobian_freezing, convergence_criteria, timeout_config, previous_state, ...)` | Newton-Raphson settings |
|
||||
| `PicardConfig` | `(max_iterations, tolerance, relaxation, initial_state, timeout_ms, convergence_criteria)` | Picard / Seq. Substitution settings |
|
||||
| `FallbackConfig` | `(newton, picard)` | Fallback behavior |
|
||||
| `ConvergenceCriteria`| `(pressure_tolerance_pa, mass_balance_tolerance_kgs, energy_balance_tolerance_w)`| Detailed component criteria |
|
||||
| `JacobianFreezingConfig`| `(max_frozen_iters, threshold)`| Speeds up Newton-Raphson |
|
||||
| `TimeoutConfig` | `(return_best_state_on_timeout, zoh_fallback)`| Behavior on timeout limit |
|
||||
|
||||
### Solver Running
|
||||
|
||||
```python
|
||||
# Newton-Raphson (fast convergence)
|
||||
config = entropyk.NewtonConfig(max_iterations=100, tolerance=1e-6, line_search=True)
|
||||
# Strategy Enum approach
|
||||
strategy = entropyk.SolverStrategy.newton(max_iterations=100, tolerance=1e-6)
|
||||
# Or
|
||||
strategy = entropyk.SolverStrategy.picard(relaxation=0.5)
|
||||
|
||||
# Picard / Sequential Substitution (more robust)
|
||||
config = entropyk.PicardConfig(max_iterations=500, tolerance=1e-4, relaxation=0.5)
|
||||
result = strategy.solve(system) # Returns ConvergedState
|
||||
|
||||
# Fallback (Newton → Picard on divergence)
|
||||
config = entropyk.FallbackConfig(newton=newton_cfg, picard=picard_cfg)
|
||||
|
||||
result = config.solve(system) # Returns ConvergedState
|
||||
# Legacy Config approach
|
||||
config = entropyk.FallbackConfig(
|
||||
newton=entropyk.NewtonConfig(max_iterations=100),
|
||||
picard=entropyk.PicardConfig(max_iterations=500)
|
||||
)
|
||||
result = config.solve(system)
|
||||
```
|
||||
|
||||
### Exceptions
|
||||
|
||||
618
bindings/python/complete_thermodynamic_system.ipynb
Normal file
618
bindings/python/complete_thermodynamic_system.ipynb
Normal file
@@ -0,0 +1,618 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Entropyk — Système Thermodynamique Complet avec Vraies Équations\n",
|
||||
"\n",
|
||||
"Ce notebook présente un **système frigorifique réaliste** avec:\n",
|
||||
"\n",
|
||||
"- **Vrais composants thermodynamiques** utilisant CoolProp\n",
|
||||
"- **Circuit frigorigène** complet\n",
|
||||
"- **Circuits eau** côté condenseur et évaporateur\n",
|
||||
"- **Contrôle inverse** pour surchauffe/sous-refroidissement"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import entropyk\n",
|
||||
"import numpy as np\n",
|
||||
"\n",
|
||||
"print(\"=== ENTROPYK - SYSTÈME THERMODYNAMIQUE AVEC VRAIES ÉQUATIONS ===\\n\")\n",
|
||||
"print(\"Composants disponibles:\")\n",
|
||||
"print([x for x in dir(entropyk) if not x.startswith('_')])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"---\n",
|
||||
"# 1. CRÉATION DES COMPOSANTS AVEC PARAMÈTRES RÉELS"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# === Paramètres du système ===\n",
|
||||
"\n",
|
||||
"FLUID = \"R134a\"\n",
|
||||
"\n",
|
||||
"# Condenseur: eau à 30°C, débit 0.5 kg/s\n",
|
||||
"COND_WATER_TEMP = 30.0 # °C\n",
|
||||
"COND_WATER_FLOW = 0.5 # kg/s\n",
|
||||
"COND_UA = 8000.0 # W/K\n",
|
||||
"\n",
|
||||
"# Évaporateur: eau à 12°C, débit 0.4 kg/s\n",
|
||||
"EVAP_WATER_TEMP = 12.0 # °C\n",
|
||||
"EVAP_WATER_FLOW = 0.4 # kg/s\n",
|
||||
"EVAP_UA = 6000.0 # W/K\n",
|
||||
"\n",
|
||||
"# Compresseur\n",
|
||||
"COMP_SPEED = 3000.0 # RPM\n",
|
||||
"COMP_DISP = 0.0001 # m³/rev (100 cc)\n",
|
||||
"COMP_EFF = 0.85 # Rendement\n",
|
||||
"\n",
|
||||
"print(\"Paramètres définis:\")\n",
|
||||
"print(f\" Fluide: {FLUID}\")\n",
|
||||
"print(f\" Condenseur: UA={COND_UA} W/K, eau={COND_WATER_TEMP}°C @ {COND_WATER_FLOW} kg/s\")\n",
|
||||
"print(f\" Évaporateur: UA={EVAP_UA} W/K, eau={EVAP_WATER_TEMP}°C @ {EVAP_WATER_FLOW} kg/s\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"---\n",
|
||||
"# 2. CIRCUIT FRIGORIGÈNE\n",
|
||||
"\n",
|
||||
"```\n",
|
||||
" ┌─────────────────────────────────────────────────────────┐\n",
|
||||
" │ CIRCUIT R134a │\n",
|
||||
" │ │\n",
|
||||
" │ [COMP] ──→ [COND] ──→ [EXV] ──→ [EVAP] ──→ [COMP] │\n",
|
||||
" │ │\n",
|
||||
" └─────────────────────────────────────────────────────────┘\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"print(\"=== CIRCUIT FRIGORIGÈNE ===\\n\")\n",
|
||||
"\n",
|
||||
"# Créer le système\n",
|
||||
"system = entropyk.System()\n",
|
||||
"\n",
|
||||
"# Compresseur avec coefficients AHRI 540\n",
|
||||
"comp = entropyk.Compressor(\n",
|
||||
" m1=0.85, m2=2.5, # Flow coefficients\n",
|
||||
" m3=500.0, m4=1500.0, m5=-2.5, m6=1.8, # Power (cooling)\n",
|
||||
" m7=600.0, m8=1600.0, m9=-3.0, m10=2.0, # Power (heating)\n",
|
||||
" speed_rpm=COMP_SPEED,\n",
|
||||
" displacement=COMP_DISP,\n",
|
||||
" efficiency=COMP_EFF,\n",
|
||||
" fluid=FLUID\n",
|
||||
")\n",
|
||||
"comp_idx = system.add_component(comp)\n",
|
||||
"print(f\"1. Compresseur: {comp}\")\n",
|
||||
"\n",
|
||||
"# Condenseur avec eau côté tube\n",
|
||||
"cond = entropyk.Condenser(\n",
|
||||
" ua=COND_UA,\n",
|
||||
" fluid=FLUID,\n",
|
||||
" water_temp=COND_WATER_TEMP,\n",
|
||||
" water_flow=COND_WATER_FLOW\n",
|
||||
")\n",
|
||||
"cond_idx = system.add_component(cond)\n",
|
||||
"print(f\"2. Condenseur: {cond}\")\n",
|
||||
"\n",
|
||||
"# Vanne d'expansion\n",
|
||||
"exv = entropyk.ExpansionValve(\n",
|
||||
" fluid=FLUID,\n",
|
||||
" opening=0.6\n",
|
||||
")\n",
|
||||
"exv_idx = system.add_component(exv)\n",
|
||||
"print(f\"3. EXV: {exv}\")\n",
|
||||
"\n",
|
||||
"# Évaporateur avec eau côté tube\n",
|
||||
"evap = entropyk.Evaporator(\n",
|
||||
" ua=EVAP_UA,\n",
|
||||
" fluid=FLUID,\n",
|
||||
" water_temp=EVAP_WATER_TEMP,\n",
|
||||
" water_flow=EVAP_WATER_FLOW\n",
|
||||
")\n",
|
||||
"evap_idx = system.add_component(evap)\n",
|
||||
"print(f\"4. Évaporateur: {evap}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"print(\"=== CONNEXIONS CYCLE FRIGO ===\\n\")\n",
|
||||
"\n",
|
||||
"# Connecter le cycle frigorigène\n",
|
||||
"system.add_edge(comp_idx, cond_idx) # Comp → Cond (HP)\n",
|
||||
"system.add_edge(cond_idx, exv_idx) # Cond → EXV\n",
|
||||
"system.add_edge(exv_idx, evap_idx) # EXV → Evap (BP)\n",
|
||||
"system.add_edge(evap_idx, comp_idx) # Evap → Comp\n",
|
||||
"\n",
|
||||
"print(\"Cycle frigorigène connecté: Comp → Cond → EXV → Evap → Comp\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"---\n",
|
||||
"# 3. CIRCUITS EAU"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"print(\"=== CIRCUIT EAU CONDENSEUR ===\\n\")\n",
|
||||
"\n",
|
||||
"# Source eau condenseur (30°C, 1 bar)\n",
|
||||
"cond_water_in = entropyk.FlowSource(\n",
|
||||
" pressure_pa=100000.0,\n",
|
||||
" temperature_k=273.15 + COND_WATER_TEMP,\n",
|
||||
" fluid=\"Water\"\n",
|
||||
")\n",
|
||||
"cond_in_idx = system.add_component(cond_water_in)\n",
|
||||
"print(f\"Source eau cond: {cond_water_in}\")\n",
|
||||
"\n",
|
||||
"# Sink eau condenseur\n",
|
||||
"cond_water_out = entropyk.FlowSink()\n",
|
||||
"cond_out_idx = system.add_component(cond_water_out)\n",
|
||||
"print(f\"Sink eau cond: {cond_water_out}\")\n",
|
||||
"\n",
|
||||
"# Connexions\n",
|
||||
"system.add_edge(cond_in_idx, cond_idx)\n",
|
||||
"system.add_edge(cond_idx, cond_out_idx)\n",
|
||||
"\n",
|
||||
"print(f\"\\nCircuit eau cond: Source({COND_WATER_TEMP}°C) → Condenseur → Sink\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"print(\"=== CIRCUIT EAU ÉVAPORATEUR ===\\n\")\n",
|
||||
"\n",
|
||||
"# Source eau évaporateur (12°C, 1 bar)\n",
|
||||
"evap_water_in = entropyk.FlowSource(\n",
|
||||
" pressure_pa=100000.0,\n",
|
||||
" temperature_k=273.15 + EVAP_WATER_TEMP,\n",
|
||||
" fluid=\"Water\"\n",
|
||||
")\n",
|
||||
"evap_in_idx = system.add_component(evap_water_in)\n",
|
||||
"print(f\"Source eau evap: {evap_water_in}\")\n",
|
||||
"\n",
|
||||
"# Sink eau évaporateur\n",
|
||||
"evap_water_out = entropyk.FlowSink()\n",
|
||||
"evap_out_idx = system.add_component(evap_water_out)\n",
|
||||
"print(f\"Sink eau evap: {evap_water_out}\")\n",
|
||||
"\n",
|
||||
"# Connexions\n",
|
||||
"system.add_edge(evap_in_idx, evap_idx)\n",
|
||||
"system.add_edge(evap_idx, evap_out_idx)\n",
|
||||
"\n",
|
||||
"print(f\"\\nCircuit eau evap: Source({EVAP_WATER_TEMP}°C) → Évaporateur → Sink\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"---\n",
|
||||
"# 4. CONTRÔLE INVERSE"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"print(\"=== ENREGISTREMENT NOMS ===\\n\")\n",
|
||||
"\n",
|
||||
"system.register_component_name(\"compressor\", comp_idx)\n",
|
||||
"system.register_component_name(\"condenser\", cond_idx)\n",
|
||||
"system.register_component_name(\"evaporator\", evap_idx)\n",
|
||||
"system.register_component_name(\"exv\", exv_idx)\n",
|
||||
"\n",
|
||||
"print(\"Noms enregistrés: compressor, condenser, evaporator, exv\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"print(\"=== CONTRAINTES DE CONTRÔLE ===\\n\")\n",
|
||||
"\n",
|
||||
"# Contrainte: Superheat = 5K\n",
|
||||
"sh_constraint = entropyk.Constraint.superheat(\n",
|
||||
" id=\"sh_5k\",\n",
|
||||
" component_id=\"evaporator\",\n",
|
||||
" target_value=5.0,\n",
|
||||
" tolerance=1e-4\n",
|
||||
")\n",
|
||||
"system.add_constraint(sh_constraint)\n",
|
||||
"print(f\"1. Surchauffe: {sh_constraint}\")\n",
|
||||
"\n",
|
||||
"# Contrainte: Subcooling = 3K\n",
|
||||
"sc_constraint = entropyk.Constraint.subcooling(\n",
|
||||
" id=\"sc_3k\",\n",
|
||||
" component_id=\"condenser\",\n",
|
||||
" target_value=3.0,\n",
|
||||
" tolerance=1e-4\n",
|
||||
")\n",
|
||||
"system.add_constraint(sc_constraint)\n",
|
||||
"print(f\"2. Sous-refroidissement: {sc_constraint}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"print(\"=== VARIABLES DE CONTRÔLE ===\\n\")\n",
|
||||
"\n",
|
||||
"# EXV opening (10% - 100%)\n",
|
||||
"exv_var = entropyk.BoundedVariable(\n",
|
||||
" id=\"exv_opening\",\n",
|
||||
" value=0.6,\n",
|
||||
" min=0.1,\n",
|
||||
" max=1.0,\n",
|
||||
" component_id=\"exv\"\n",
|
||||
")\n",
|
||||
"system.add_bounded_variable(exv_var)\n",
|
||||
"print(f\"1. EXV: {exv_var}\")\n",
|
||||
"\n",
|
||||
"# Compressor speed (1500 - 6000 RPM)\n",
|
||||
"speed_var = entropyk.BoundedVariable(\n",
|
||||
" id=\"comp_speed\",\n",
|
||||
" value=3000.0,\n",
|
||||
" min=1500.0,\n",
|
||||
" max=6000.0,\n",
|
||||
" component_id=\"compressor\"\n",
|
||||
")\n",
|
||||
"system.add_bounded_variable(speed_var)\n",
|
||||
"print(f\"2. Vitesse: {speed_var}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"print(\"=== LIENS CONTRAINTES → VARIABLES ===\\n\")\n",
|
||||
"\n",
|
||||
"system.link_constraint_to_control(\"sh_5k\", \"exv_opening\")\n",
|
||||
"print(\"1. Superheat → EXV opening\")\n",
|
||||
"\n",
|
||||
"system.link_constraint_to_control(\"sc_3k\", \"comp_speed\")\n",
|
||||
"print(\"2. Subcooling → Compressor speed\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"---\n",
|
||||
"# 5. FINALISATION ET RÉSOLUTION"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"print(\"=== FINALISATION ===\\n\")\n",
|
||||
"\n",
|
||||
"system.finalize()\n",
|
||||
"\n",
|
||||
"print(f\"Système finalisé:\")\n",
|
||||
"print(f\" Composants: {system.node_count}\")\n",
|
||||
"print(f\" Connexions: {system.edge_count}\")\n",
|
||||
"print(f\" Variables d'état: {system.state_vector_len}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"print(\"=== CONFIGURATION SOLVEUR ===\\n\")\n",
|
||||
"\n",
|
||||
"# Newton avec line search\n",
|
||||
"newton = entropyk.NewtonConfig(\n",
|
||||
" max_iterations=500,\n",
|
||||
" tolerance=1e-8,\n",
|
||||
" line_search=True,\n",
|
||||
" timeout_ms=60000\n",
|
||||
")\n",
|
||||
"print(f\"Newton: {newton}\")\n",
|
||||
"\n",
|
||||
"# Picard en backup\n",
|
||||
"picard = entropyk.PicardConfig(\n",
|
||||
" max_iterations=1000,\n",
|
||||
" tolerance=1e-6,\n",
|
||||
" relaxation=0.3\n",
|
||||
")\n",
|
||||
"print(f\"Picard: {picard}\")\n",
|
||||
"\n",
|
||||
"# Fallback\n",
|
||||
"solver = entropyk.FallbackConfig(newton=newton, picard=picard)\n",
|
||||
"print(f\"\\nSolver: {solver}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"print(\"=== RÉSOLUTION ===\\n\")\n",
|
||||
"\n",
|
||||
"try:\n",
|
||||
" result = solver.solve(system)\n",
|
||||
" \n",
|
||||
" print(f\"✅ RÉSULTAT:\")\n",
|
||||
" print(f\" Itérations: {result.iterations}\")\n",
|
||||
" print(f\" Résidu: {result.final_residual:.2e}\")\n",
|
||||
" print(f\" Statut: {result.status}\")\n",
|
||||
" print(f\" Convergé: {result.is_converged}\")\n",
|
||||
" \n",
|
||||
" # State vector\n",
|
||||
" state = result.to_numpy()\n",
|
||||
" print(f\"\\n State vector: {state.shape}\")\n",
|
||||
" print(f\" Valeurs: min={state.min():.2f}, max={state.max():.2f}\")\n",
|
||||
" \n",
|
||||
" if result.status == \"ControlSaturation\":\n",
|
||||
" print(\"\\n ⚠️ Saturation de contrôle - variable à la borne\")\n",
|
||||
" \n",
|
||||
"except entropyk.SolverError as e:\n",
|
||||
" print(f\"❌ SolverError: {e}\")\n",
|
||||
" \n",
|
||||
"except entropyk.TimeoutError as e:\n",
|
||||
" print(f\"⏱️ TimeoutError: {e}\")\n",
|
||||
" \n",
|
||||
"except Exception as e:\n",
|
||||
" print(f\"❌ Erreur: {type(e).__name__}: {e}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"---\n",
|
||||
"# 6. TESTS AVEC DIFFÉRENTS FLUIDES"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"print(\"=== TEST MULTI-FLUIDES ===\\n\")\n",
|
||||
"\n",
|
||||
"fluids = [\n",
|
||||
" (\"R134a\", 12.0, 30.0), # T_cond_in, T_evap_in\n",
|
||||
" (\"R32\", 12.0, 30.0),\n",
|
||||
" (\"R290\", 12.0, 30.0), # Propane\n",
|
||||
" (\"R744\", 12.0, 30.0), # CO2\n",
|
||||
" (\"R1234yf\", 12.0, 30.0), # HFO\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"for fluid, t_evap, t_cond in fluids:\n",
|
||||
" try:\n",
|
||||
" s = entropyk.System()\n",
|
||||
" \n",
|
||||
" c = s.add_component(entropyk.Compressor(\n",
|
||||
" speed_rpm=3000, displacement=0.0001, efficiency=0.85, fluid=fluid\n",
|
||||
" ))\n",
|
||||
" cd = s.add_component(entropyk.Condenser(ua=8000, fluid=fluid, water_temp=t_cond, water_flow=0.5))\n",
|
||||
" ex = s.add_component(entropyk.ExpansionValve(fluid=fluid, opening=0.5))\n",
|
||||
" ev = s.add_component(entropyk.Evaporator(ua=6000, fluid=fluid, water_temp=t_evap, water_flow=0.4))\n",
|
||||
" \n",
|
||||
" s.add_edge(c, cd)\n",
|
||||
" s.add_edge(cd, ex)\n",
|
||||
" s.add_edge(ex, ev)\n",
|
||||
" s.add_edge(ev, c)\n",
|
||||
" \n",
|
||||
" # Eau circuits\n",
|
||||
" cd_in = s.add_component(entropyk.FlowSource(pressure_pa=100000, temperature_k=273.15+t_cond, fluid=\"Water\"))\n",
|
||||
" cd_out = s.add_component(entropyk.FlowSink())\n",
|
||||
" ev_in = s.add_component(entropyk.FlowSource(pressure_pa=100000, temperature_k=273.15+t_evap, fluid=\"Water\"))\n",
|
||||
" ev_out = s.add_component(entropyk.FlowSink())\n",
|
||||
" \n",
|
||||
" s.add_edge(cd_in, cd)\n",
|
||||
" s.add_edge(cd, cd_out)\n",
|
||||
" s.add_edge(ev_in, ev)\n",
|
||||
" s.add_edge(ev, ev_out)\n",
|
||||
" \n",
|
||||
" s.finalize()\n",
|
||||
" \n",
|
||||
" print(f\" {fluid:10s} → ✅ OK ({s.state_vector_len} vars)\")\n",
|
||||
" \n",
|
||||
" except Exception as e:\n",
|
||||
" print(f\" {fluid:10s} → ❌ {e}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"---\n",
|
||||
"# 7. TYPES PHYSIQUES"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"print(\"=== TYPES PHYSIQUES ===\\n\")\n",
|
||||
"\n",
|
||||
"# Pressions cycle R134a typique\n",
|
||||
"p_hp = entropyk.Pressure(bar=12.0) # HP condensation\n",
|
||||
"p_bp = entropyk.Pressure(bar=2.0) # BP évaporation\n",
|
||||
"\n",
|
||||
"print(f\"Pressions:\")\n",
|
||||
"print(f\" HP: {p_hp} = {p_hp.to_kpa():.0f} kPa\")\n",
|
||||
"print(f\" BP: {p_bp} = {p_bp.to_kpa():.0f} kPa\")\n",
|
||||
"print(f\" Ratio: {p_hp.to_bar()/p_bp.to_bar():.1f}\")\n",
|
||||
"\n",
|
||||
"# Températures\n",
|
||||
"t_cond = entropyk.Temperature(celsius=45.0)\n",
|
||||
"t_evap = entropyk.Temperature(celsius=-5.0)\n",
|
||||
"\n",
|
||||
"print(f\"\\nTempératures:\")\n",
|
||||
"print(f\" Condensation: {t_cond.to_celsius():.0f}°C = {t_cond.to_kelvin():.2f} K\")\n",
|
||||
"print(f\" Évaporation: {t_evap.to_celsius():.0f}°C = {t_evap.to_kelvin():.2f} K\")\n",
|
||||
"\n",
|
||||
"# Enthalpies\n",
|
||||
"h_liq = entropyk.Enthalpy(kj_per_kg=250.0)\n",
|
||||
"h_vap = entropyk.Enthalpy(kj_per_kg=400.0)\n",
|
||||
"\n",
|
||||
"print(f\"\\nEnthalpies:\")\n",
|
||||
"print(f\" Liquide: {h_liq.to_kj_per_kg():.0f} kJ/kg\")\n",
|
||||
"print(f\" Vapeur: {h_vap.to_kj_per_kg():.0f} kJ/kg\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"---\n",
|
||||
"# 8. SCHÉMA DU SYSTÈME COMPLET\n",
|
||||
"\n",
|
||||
"```\n",
|
||||
"╔═══════════════════════════════════════════════════════════════════════════╗\n",
|
||||
"║ SYSTÈME FRIGORIFIQUE COMPLET ║\n",
|
||||
"╠═══════════════════════════════════════════════════════════════════════════╣\n",
|
||||
"║ ║\n",
|
||||
"║ CIRCUIT EAU CONDENSEUR ║\n",
|
||||
"║ ┌─────────┐ ┌─────────┐ ║\n",
|
||||
"║ │ SOURCE │───────[TUBE]────────→│ SINK │ ║\n",
|
||||
"║ │ 30°C │ ╱╲ │ 35°C │ ║\n",
|
||||
"║ └─────────┘ ╱ ╲ └─────────┘ ║\n",
|
||||
"║ ╱COND╲ (Échange thermique) ║\n",
|
||||
"║ HP, 45°C → ╲──────╱ ← Liquide, 40°C ║\n",
|
||||
"║ ╲ ╱ ║\n",
|
||||
"║ ╲──╱ ║\n",
|
||||
"║ ║ ║\n",
|
||||
"║ ┌─────────┐ ║ ┌─────────┐ ┌─────────┐ ║\n",
|
||||
"║ │ COMP │──────║──────│ COND │──────│ EXV │ ║\n",
|
||||
"║ │ R134a │ ║ │ │ │ │ ║\n",
|
||||
"║ └─────────┘ ║ └─────────┘ └─────────┘ ║\n",
|
||||
"║ ↑ ║ │ ║\n",
|
||||
"║ │ ║ ↓ ║\n",
|
||||
"║ │ ║ BP, 5°C ║\n",
|
||||
"║ │ ║ │ ║\n",
|
||||
"║ │ ║ ↓ ║\n",
|
||||
"║ │ ╱╲╱╲ ┌─────────┐ ║\n",
|
||||
"║ │ ╱ EVAP╲ │ │ ║\n",
|
||||
"║ └────────╲──────╱←────────────────────────────────│ │ ║\n",
|
||||
"║ Vapeur, 5°C ╲ ╱ Vapeur, -5°C (2-phase) └─────────┘ ║\n",
|
||||
"║ ╲──╱ ║\n",
|
||||
"║ ╱ ╲ (Échange thermique) ║\n",
|
||||
"║ ╱ ╲ ║\n",
|
||||
"║ CIRCUIT EAU ╱────────╲ ┌─────────┐ ║\n",
|
||||
"║ ÉVAPORATEUR [TUBE]────→│ SINK │ ║\n",
|
||||
"║ ┌─────────┐ 7°C │ │ ║\n",
|
||||
"║ │ SOURCE │──────────→│ │ ║\n",
|
||||
"║ │ 12°C │ └─────────┘ ║\n",
|
||||
"║ └─────────┘ ║\n",
|
||||
"║ ║\n",
|
||||
"╚═══════════════════════════════════════════════════════════════════════════╝\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"print(\"\"\"\n",
|
||||
"╔══════════════════════════════════════════════════════════════════════════════╗\n",
|
||||
"║ RÉSUMÉ API ENTROPYK ║\n",
|
||||
"╠══════════════════════════════════════════════════════════════════════════════╣\n",
|
||||
"║ ║\n",
|
||||
"║ COMPOSANTS (avec vraies équations CoolProp) ║\n",
|
||||
"║ ───────────────────────────────────────────────────────────────────────── ║\n",
|
||||
"║ Compressor(fluid, speed_rpm, displacement, efficiency, m1-m10) ║\n",
|
||||
"║ Condenser(ua, fluid, water_temp, water_flow) ← avec circuit eau ║\n",
|
||||
"║ Evaporator(ua, fluid, water_temp, water_flow) ← avec circuit eau ║\n",
|
||||
"║ ExpansionValve(fluid, opening) ║\n",
|
||||
"║ FlowSource(pressure_pa, temperature_k, fluid) ║\n",
|
||||
"║ FlowSink() ║\n",
|
||||
"║ ║\n",
|
||||
"║ CIRCUITS ║\n",
|
||||
"║ ───────────────────────────────────────────────────────────────────────── ║\n",
|
||||
"║ system.add_edge(src_idx, tgt_idx) # Connecter composants ║\n",
|
||||
"║ ║\n",
|
||||
"║ FLUIDES DISPONIBLES (66+) ║\n",
|
||||
"║ ───────────────────────────────────────────────────────────────────────── ║\n",
|
||||
"║ HFC: R134a, R410A, R32, R407C, R125, R143a, R22, etc. ║\n",
|
||||
"║ HFO: R1234yf, R1234ze(E), R1233zd(E), R1336mzz(E) ║\n",
|
||||
"║ Naturels: R744 (CO2), R290 (Propane), R600a (Isobutane), R717 (Ammonia) ║\n",
|
||||
"║ Mélanges: R513A, R454B, R452B, R507A ║\n",
|
||||
"║ ║\n",
|
||||
"╚══════════════════════════════════════════════════════════════════════════════╝\n",
|
||||
"\"\"\")"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.0"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
221
bindings/python/examples/complete_thermodynamic_system.py
Normal file
221
bindings/python/examples/complete_thermodynamic_system.py
Normal file
@@ -0,0 +1,221 @@
|
||||
import entropyk
|
||||
import math
|
||||
|
||||
def build_complete_system():
|
||||
# ── 1. Initialisation du graphe du système ──
|
||||
system = entropyk.System()
|
||||
print("Construction du système Entropyk complet...")
|
||||
|
||||
# Paramètres fluides
|
||||
refrigerant = "R410A"
|
||||
water = "Water"
|
||||
|
||||
# =========================================================================
|
||||
# BOUCLE 1 : CIRCUIT FRIGORIFIQUE (REFRIGERANT R410A)
|
||||
# =========================================================================
|
||||
|
||||
# 1.1 Compresseur (Modèle Polynomial AHRI 540)
|
||||
compressor = system.add_component(entropyk.Compressor(
|
||||
m1=0.85, m2=2.5, m3=500.0, m4=1500.0, m5=-2.5, m6=1.8, m7=600.0, m8=1600.0, m9=-3.0, m10=2.0,
|
||||
speed_rpm=3600.0,
|
||||
displacement=0.00008,
|
||||
efficiency=0.88,
|
||||
fluid=refrigerant
|
||||
))
|
||||
|
||||
# 1.2 Tuyau de refoulement (vers condenseur)
|
||||
pipe_hot = system.add_component(entropyk.Pipe(
|
||||
length=5.0,
|
||||
diameter=0.02,
|
||||
fluid=refrigerant
|
||||
))
|
||||
|
||||
# 1.3 Condenseur (Rejet de chaleur)
|
||||
condenser = system.add_component(entropyk.Condenser(
|
||||
ua=4500.0,
|
||||
fluid=refrigerant,
|
||||
water_temp=30.0, # Température d'entrée côté eau/air
|
||||
water_flow=2.0
|
||||
))
|
||||
|
||||
# 1.4 Ligne liquide
|
||||
pipe_liquid = system.add_component(entropyk.Pipe(
|
||||
length=10.0,
|
||||
diameter=0.015,
|
||||
fluid=refrigerant
|
||||
))
|
||||
|
||||
# 1.5 Division du débit (FlowSplitter) vers 2 évaporateurs
|
||||
splitter = system.add_component(entropyk.FlowSplitter(n_outlets=2))
|
||||
|
||||
# 1.6 Branche A : Détendeur + Évaporateur 1
|
||||
exv_a = system.add_component(entropyk.ExpansionValve(fluid=refrigerant, opening=0.5))
|
||||
evap_a = system.add_component(entropyk.Evaporator(
|
||||
ua=2000.0,
|
||||
fluid=refrigerant,
|
||||
water_temp=12.0,
|
||||
water_flow=1.0
|
||||
))
|
||||
|
||||
# 1.7 Branche B : Détendeur + Évaporateur 2
|
||||
exv_b = system.add_component(entropyk.ExpansionValve(fluid=refrigerant, opening=0.5))
|
||||
evap_b = system.add_component(entropyk.Evaporator(
|
||||
ua=2000.0,
|
||||
fluid=refrigerant,
|
||||
water_temp=15.0, # Température d'eau légèrement différente
|
||||
water_flow=1.0
|
||||
))
|
||||
|
||||
# 1.8 Fusion du débit (FlowMerger)
|
||||
merger = system.add_component(entropyk.FlowMerger(n_inlets=2))
|
||||
|
||||
# 1.9 Tuyau d'aspiration (retour compresseur)
|
||||
pipe_suction = system.add_component(entropyk.Pipe(
|
||||
length=5.0,
|
||||
diameter=0.025,
|
||||
fluid=refrigerant
|
||||
))
|
||||
|
||||
# --- Connexions de la boucle frigo ---
|
||||
system.add_edge(compressor, pipe_hot)
|
||||
system.add_edge(pipe_hot, condenser)
|
||||
system.add_edge(condenser, pipe_liquid)
|
||||
|
||||
# Splitter
|
||||
system.add_edge(pipe_liquid, splitter)
|
||||
system.add_edge(splitter, exv_a)
|
||||
system.add_edge(splitter, exv_b)
|
||||
|
||||
# Branches parallèles
|
||||
system.add_edge(exv_a, evap_a)
|
||||
system.add_edge(exv_b, evap_b)
|
||||
|
||||
# Merger
|
||||
system.add_edge(evap_a, merger)
|
||||
system.add_edge(evap_b, merger)
|
||||
|
||||
system.add_edge(merger, pipe_suction)
|
||||
system.add_edge(pipe_suction, compressor)
|
||||
|
||||
# =========================================================================
|
||||
# BOUCLE 2 : CIRCUIT RÉSEAU HYDRAULIQUE (EAU - Côté Évaporateur Principal)
|
||||
# (Juste de la tuyauterie et une pompe pour montrer les FlowSource/FlowSink)
|
||||
# =========================================================================
|
||||
|
||||
water_source = system.add_component(entropyk.FlowSource(
|
||||
fluid=water,
|
||||
pressure_pa=101325.0, # 1 atm
|
||||
temperature_k=285.15 # 12 °C
|
||||
))
|
||||
|
||||
water_pump = system.add_component(entropyk.Pump(
|
||||
pressure_rise_pa=50000.0, # 0.5 bar
|
||||
efficiency=0.6
|
||||
))
|
||||
|
||||
water_pipe = system.add_component(entropyk.Pipe(
|
||||
length=20.0,
|
||||
diameter=0.05,
|
||||
fluid=water
|
||||
))
|
||||
|
||||
water_sink = system.add_component(entropyk.FlowSink())
|
||||
|
||||
# --- Connexions Hydrauliques Principales ---
|
||||
system.add_edge(water_source, water_pump)
|
||||
system.add_edge(water_pump, water_pipe)
|
||||
system.add_edge(water_pipe, water_sink)
|
||||
|
||||
|
||||
# =========================================================================
|
||||
# BOUCLE 3 : CIRCUIT VENTILATION (AIR - Côté Condenseur)
|
||||
# =========================================================================
|
||||
|
||||
air_source = system.add_component(entropyk.FlowSource(
|
||||
fluid="Air",
|
||||
pressure_pa=101325.0,
|
||||
temperature_k=308.15 # 35 °C d'air ambiant
|
||||
))
|
||||
|
||||
condenser_fan = system.add_component(entropyk.Fan(
|
||||
pressure_rise_pa=200.0, # 200 Pa de montée en pression par le ventilo
|
||||
efficiency=0.5
|
||||
))
|
||||
|
||||
air_sink = system.add_component(entropyk.FlowSink())
|
||||
|
||||
# --- Connexions Ventilation ---
|
||||
system.add_edge(air_source, condenser_fan)
|
||||
system.add_edge(condenser_fan, air_sink)
|
||||
|
||||
|
||||
# ── 4. Finalisation du système ──
|
||||
print("Finalisation du graphe (Construction de la topologie)...")
|
||||
system.finalize()
|
||||
print(f"Propriétés du système : {system.node_count} composants, {system.edge_count} connexions.")
|
||||
print(f"Taille du vecteur d'état mathématique : {system.state_vector_len} variables.")
|
||||
|
||||
return system
|
||||
|
||||
|
||||
def solve_system(system):
|
||||
# ── 5. Configuration Avancée du Solveur (Story 6-6) ──
|
||||
print("\nConfiguration de la stratégie de résolution...")
|
||||
|
||||
# (Optionnel) Critères de convergence fins
|
||||
convergence = entropyk.ConvergenceCriteria(
|
||||
pressure_tolerance_pa=5.0,
|
||||
mass_balance_tolerance_kgs=1e-6,
|
||||
energy_balance_tolerance_w=1e-3
|
||||
)
|
||||
|
||||
# (Optionnel) Jacobian Freezing pour aller plus vite
|
||||
freezing = entropyk.JacobianFreezingConfig(
|
||||
max_frozen_iters=4,
|
||||
threshold=0.1
|
||||
)
|
||||
|
||||
# Configuration Newton avec tolérances avancées
|
||||
newton_config = entropyk.NewtonConfig(
|
||||
max_iterations=150,
|
||||
tolerance=1e-5,
|
||||
line_search=True,
|
||||
use_numerical_jacobian=True,
|
||||
jacobian_freezing=freezing,
|
||||
convergence_criteria=convergence,
|
||||
initial_state=[1000000.0, 450000.0] * 17
|
||||
)
|
||||
|
||||
# Configuration Picard robuste en cas d'échec de Newton
|
||||
picard_config = entropyk.PicardConfig(
|
||||
max_iterations=500,
|
||||
tolerance=1e-4,
|
||||
relaxation=0.4,
|
||||
convergence_criteria=convergence
|
||||
)
|
||||
|
||||
# ── 6. Lancement du calcul ──
|
||||
print("Lancement de la simulation (Newton uniquement)...")
|
||||
try:
|
||||
result = newton_config.solve(system)
|
||||
|
||||
status = result.status
|
||||
print(f"\n✅ Simulation terminée avec succès !")
|
||||
print(f"Statut : {status}")
|
||||
print(f"Itérations : {result.iterations}")
|
||||
print(f"Résidu final : {result.final_residual:.2e}")
|
||||
|
||||
# Le résultat contient le vecteur d'état complet
|
||||
state_vec = result.state_vector
|
||||
print(f"Aperçu des 5 premières variables d'état : {state_vec[:5]}")
|
||||
|
||||
except entropyk.TimeoutError:
|
||||
print("\n❌ Le solveur a dépassé le temps imparti (Timeout).")
|
||||
except entropyk.SolverError as e:
|
||||
print(f"\n❌ Erreur du solveur : {e}")
|
||||
print("Note: Ce comportement peut arriver si les paramètres (taille des tuyaux, coeffs, températures)")
|
||||
print("dépassent le domaine thermodynamique du fluide ou si le graphe manque de contraintes aux limites.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
system = build_complete_system()
|
||||
solve_system(system)
|
||||
87
bindings/python/examples/simple_thermodynamic_loop.py
Normal file
87
bindings/python/examples/simple_thermodynamic_loop.py
Normal file
@@ -0,0 +1,87 @@
|
||||
"""
|
||||
Cycle frigorifique simple R134a - 4 composants
|
||||
Compresseur → Condenseur → Détendeur → Évaporateur → (retour)
|
||||
|
||||
Équations des composants mock (python_components.rs) :
|
||||
Compresseur : r[0] = p_disc - (p_suc + 1 MPa) r[1] = h_disc - (h_suc + power/m_dot)
|
||||
Condenseur : r[0] = p_out - p_in r[1] = h_out - (h_in - 225 kJ/kg)
|
||||
Détendeur : r[0] = p_out - (p_in - 1 MPa) r[1] = h_out - h_in
|
||||
Évaporateur : r[0] = p_out - p_in r[1] = h_out - (h_in + 150 kJ/kg)
|
||||
|
||||
État initial cohérent : P_HP - P_LP = 1 MPa → tous les résidus de pression valent 0 au départ.
|
||||
"""
|
||||
import entropyk
|
||||
import time
|
||||
|
||||
def main():
|
||||
system = entropyk.System()
|
||||
print("Construction du système simple (R134a)...")
|
||||
|
||||
fluid = "R134a"
|
||||
|
||||
comp = system.add_component(entropyk.Compressor(
|
||||
m1=0.85, m2=2.5, m3=500.0, m4=1500.0, m5=-2.5, m6=1.8,
|
||||
m7=600.0, m8=1600.0, m9=-3.0, m10=2.0,
|
||||
speed_rpm=3600.0, displacement=0.00008, efficiency=0.88, fluid=fluid
|
||||
))
|
||||
cond = system.add_component(entropyk.Condenser(
|
||||
ua=5000.0, fluid=fluid, water_temp=30.0, water_flow=2.0
|
||||
))
|
||||
valve = system.add_component(entropyk.ExpansionValve(
|
||||
fluid=fluid, opening=0.5 # target_dp = 2e6*(1-0.5) = 1 MPa
|
||||
))
|
||||
evap = system.add_component(entropyk.Evaporator(
|
||||
ua=3000.0, fluid=fluid, water_temp=10.0, water_flow=2.0
|
||||
))
|
||||
|
||||
system.add_edge(comp, cond) # edge 0 : comp → cond
|
||||
system.add_edge(cond, valve) # edge 1 : cond → valve
|
||||
system.add_edge(valve, evap) # edge 2 : valve → evap
|
||||
system.add_edge(evap, comp) # edge 3 : evap → comp
|
||||
|
||||
system.finalize()
|
||||
print(f"Propriétés: {system.node_count} composants, {system.edge_count} connexions, "
|
||||
f"{system.state_vector_len} variables d'état.")
|
||||
|
||||
# ─── État initial cohérent avec les équations mock ───────────────────────
|
||||
# Valve et Comp utilisent tous les deux target_dp = 1 MPa
|
||||
# → P_HP - P_LP = 1 MPa ⇒ résidus de pression = 0 dès le départ
|
||||
# Enthalpies choisies proches de l'équilibre attendu :
|
||||
# Cond : h_in - 225 kJ/kg = h_out_cond (225 000 J/kg)
|
||||
# Evap : h_in + 150 kJ/kg = h_out_evap (150 000 J/kg)
|
||||
# Comp : h_in + w_sp ≈ h_in + 75 kJ/kg (AHRI 540)
|
||||
P_LP = 350_000.0 # Pa — basse pression (R134a ~1.5°C sat)
|
||||
P_HP = 1_350_000.0 # Pa — haute pression = P_LP + 1 MPa ✓
|
||||
initial_state = [
|
||||
P_HP, 485_000.0, # edge0 comp→cond : vapeur surchauffée HP (≈h_suc + 75 kJ/kg)
|
||||
P_HP, 260_000.0, # edge1 cond→valve : liquide HP (≈485-225)
|
||||
P_LP, 260_000.0, # edge2 valve→evap : biphasique BP (isenthalpique)
|
||||
P_LP, 410_000.0, # edge3 evap→comp : vapeur surchauffée BP (≈260+150)
|
||||
]
|
||||
|
||||
config = entropyk.NewtonConfig(
|
||||
max_iterations=150,
|
||||
tolerance=1e-4,
|
||||
line_search=True,
|
||||
use_numerical_jacobian=True,
|
||||
initial_state=initial_state
|
||||
)
|
||||
|
||||
print("Lancement du Newton Solver...")
|
||||
t0 = time.time()
|
||||
try:
|
||||
res = config.solve(system)
|
||||
elapsed = time.time() - t0
|
||||
print(f"\n✅ Convergé en {res.iterations} itérations ({elapsed*1000:.1f} ms)")
|
||||
sv = res.state_vector
|
||||
print(f"\nÉtat final du cycle R134a :")
|
||||
print(f" comp → cond : P={sv[0]/1e5:.2f} bar, h={sv[1]/1e3:.1f} kJ/kg")
|
||||
print(f" cond → valve : P={sv[2]/1e5:.2f} bar, h={sv[3]/1e3:.1f} kJ/kg")
|
||||
print(f" valve → evap : P={sv[4]/1e5:.2f} bar, h={sv[5]/1e3:.1f} kJ/kg")
|
||||
print(f" evap → comp : P={sv[6]/1e5:.2f} bar, h={sv[7]/1e3:.1f} kJ/kg")
|
||||
except Exception as e:
|
||||
elapsed = time.time() - t0
|
||||
print(f"\n❌ Échec après {elapsed*1000:.1f} ms : {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
11
bindings/python/print_eqs.py
Normal file
11
bindings/python/print_eqs.py
Normal file
@@ -0,0 +1,11 @@
|
||||
import entropyk
|
||||
system = entropyk.System()
|
||||
|
||||
def check(comp, n):
|
||||
c = system.add_component(comp)
|
||||
system.add_edge(c,c) # dummy
|
||||
system.finalize()
|
||||
eqs = system.graph.node_weight(c).n_equations()
|
||||
print(f"{n}: {eqs}")
|
||||
|
||||
# Wait, no python API for n_equations... Let's just create a full rust test.
|
||||
@@ -20,6 +20,7 @@ dependencies = [
|
||||
"ipykernel>=6.31.0",
|
||||
"maturin>=1.12.4",
|
||||
"numpy>=2.0.2",
|
||||
"pandas>=2.3.3",
|
||||
]
|
||||
|
||||
[tool.maturin]
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
//! Python wrappers for Entropyk thermodynamic components.
|
||||
//!
|
||||
//! Components are wrapped with simplified Pythonic constructors.
|
||||
//! Type-state–based components (Compressor, ExpansionValve, Pipe) use
|
||||
//! `SimpleAdapter` wrappers that bridge between Python construction and
|
||||
//! the Rust system's `Component` trait. These adapters store config and
|
||||
//! produce correct equation counts for the solver graph.
|
||||
//!
|
||||
//! Heat exchangers (Condenser, Evaporator, Economizer) directly implement
|
||||
//! `Component` so they use the real Rust types.
|
||||
//! Real thermodynamic components use the python_components module
|
||||
//! with actual CoolProp-based physics.
|
||||
|
||||
use pyo3::exceptions::PyValueError;
|
||||
use pyo3::prelude::*;
|
||||
@@ -17,66 +12,12 @@ use entropyk_components::{
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// Simple component adapter — implements Component directly
|
||||
// Compressor - Real AHRI 540 Implementation
|
||||
// =============================================================================
|
||||
|
||||
/// A thin adapter that implements `Component` with configurable equation counts.
|
||||
/// Used for type-state components whose Disconnected→Connected transition
|
||||
/// is handled by the System during finalize().
|
||||
struct SimpleAdapter {
|
||||
name: String,
|
||||
n_equations: usize,
|
||||
}
|
||||
|
||||
impl SimpleAdapter {
|
||||
fn new(name: &str, n_equations: usize) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
n_equations,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for SimpleAdapter {
|
||||
fn compute_residuals(
|
||||
&self,
|
||||
_state: &SystemState,
|
||||
residuals: &mut ResidualVector,
|
||||
) -> Result<(), ComponentError> {
|
||||
for r in residuals.iter_mut() {
|
||||
*r = 0.0;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn jacobian_entries(
|
||||
&self,
|
||||
_state: &SystemState,
|
||||
_jacobian: &mut JacobianBuilder,
|
||||
) -> Result<(), ComponentError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn n_equations(&self) -> usize {
|
||||
self.n_equations
|
||||
}
|
||||
|
||||
fn get_ports(&self) -> &[ConnectedPort] {
|
||||
&[]
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for SimpleAdapter {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "SimpleAdapter({})", self.name)
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Compressor
|
||||
// =============================================================================
|
||||
|
||||
/// A compressor component using AHRI 540 performance model.
|
||||
/// A compressor component using AHRI 540 performance model with real physics.
|
||||
///
|
||||
/// Uses CoolProp for thermodynamic property calculations.
|
||||
///
|
||||
/// Example::
|
||||
///
|
||||
@@ -91,13 +32,8 @@ impl std::fmt::Debug for SimpleAdapter {
|
||||
/// )
|
||||
#[pyclass(name = "Compressor", module = "entropyk")]
|
||||
#[derive(Clone)]
|
||||
#[allow(dead_code)] // Fields reserved until SimpleAdapter type-state migration
|
||||
pub struct PyCompressor {
|
||||
pub(crate) coefficients: entropyk::Ahri540Coefficients,
|
||||
pub(crate) speed_rpm: f64,
|
||||
pub(crate) displacement: f64,
|
||||
pub(crate) efficiency: f64,
|
||||
pub(crate) fluid: String,
|
||||
pub(crate) inner: entropyk_components::PyCompressorReal,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
@@ -141,75 +77,83 @@ impl PyCompressor {
|
||||
"efficiency must be between 0.0 and 1.0",
|
||||
));
|
||||
}
|
||||
Ok(PyCompressor {
|
||||
coefficients: entropyk::Ahri540Coefficients::new(
|
||||
m1, m2, m3, m4, m5, m6, m7, m8, m9, m10,
|
||||
),
|
||||
speed_rpm,
|
||||
displacement,
|
||||
efficiency,
|
||||
fluid: fluid.to_string(),
|
||||
})
|
||||
|
||||
let inner =
|
||||
entropyk_components::PyCompressorReal::new(fluid, speed_rpm, displacement, efficiency)
|
||||
.with_coefficients(m1, m2, m3, m4, m5, m6, m7, m8, m9, m10);
|
||||
|
||||
Ok(PyCompressor { inner })
|
||||
}
|
||||
|
||||
/// AHRI 540 coefficients.
|
||||
/// Speed in RPM.
|
||||
#[getter]
|
||||
fn speed(&self) -> f64 {
|
||||
self.speed_rpm
|
||||
self.inner.speed_rpm
|
||||
}
|
||||
|
||||
/// Isentropic efficiency (0–1).
|
||||
#[getter]
|
||||
fn efficiency_value(&self) -> f64 {
|
||||
self.efficiency
|
||||
self.inner.efficiency
|
||||
}
|
||||
|
||||
/// Fluid name.
|
||||
#[getter]
|
||||
fn fluid_name(&self) -> &str {
|
||||
&self.fluid
|
||||
fn fluid_name(&self) -> String {
|
||||
self.inner.fluid.0.clone()
|
||||
}
|
||||
|
||||
fn __repr__(&self) -> String {
|
||||
format!(
|
||||
"Compressor(speed={:.0} RPM, η={:.2}, fluid={})",
|
||||
self.speed_rpm, self.efficiency, self.fluid
|
||||
self.inner.speed_rpm, self.inner.efficiency, self.inner.fluid.0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl PyCompressor {
|
||||
pub(crate) fn build(&self) -> Box<dyn Component> {
|
||||
// Compressor uses type-state pattern; adapter provides 2 equations
|
||||
// (mass flow + energy balance). Real physics computed during solve.
|
||||
Box::new(SimpleAdapter::new("Compressor", 2))
|
||||
Box::new(self.inner.clone())
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Condenser
|
||||
// Condenser - Real Heat Exchanger with Water Side
|
||||
// =============================================================================
|
||||
|
||||
/// A condenser (heat rejection) component.
|
||||
/// A condenser with water-side heat transfer.
|
||||
///
|
||||
/// Uses ε-NTU method with CoolProp for refrigerant properties.
|
||||
///
|
||||
/// Example::
|
||||
///
|
||||
/// cond = Condenser(ua=5000.0)
|
||||
/// cond = Condenser(ua=5000.0, fluid="R134a", water_temp=30.0, water_flow=0.5)
|
||||
#[pyclass(name = "Condenser", module = "entropyk")]
|
||||
#[derive(Clone)]
|
||||
pub struct PyCondenser {
|
||||
pub(crate) ua: f64,
|
||||
pub(crate) fluid: String,
|
||||
pub(crate) water_temp: f64,
|
||||
pub(crate) water_flow: f64,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyCondenser {
|
||||
#[new]
|
||||
#[pyo3(signature = (ua=5000.0))]
|
||||
fn new(ua: f64) -> PyResult<Self> {
|
||||
#[pyo3(signature = (ua=5000.0, fluid="R134a", water_temp=30.0, water_flow=0.5))]
|
||||
fn new(ua: f64, fluid: &str, water_temp: f64, water_flow: f64) -> PyResult<Self> {
|
||||
if ua <= 0.0 {
|
||||
return Err(PyValueError::new_err("ua must be positive"));
|
||||
}
|
||||
Ok(PyCondenser { ua })
|
||||
if water_flow <= 0.0 {
|
||||
return Err(PyValueError::new_err("water_flow must be positive"));
|
||||
}
|
||||
Ok(PyCondenser {
|
||||
ua,
|
||||
fluid: fluid.to_string(),
|
||||
water_temp,
|
||||
water_flow,
|
||||
})
|
||||
}
|
||||
|
||||
/// Thermal conductance UA in W/K.
|
||||
@@ -219,40 +163,61 @@ impl PyCondenser {
|
||||
}
|
||||
|
||||
fn __repr__(&self) -> String {
|
||||
format!("Condenser(UA={:.1} W/K)", self.ua)
|
||||
format!(
|
||||
"Condenser(UA={:.1} W/K, fluid={}, water={:.1}°C)",
|
||||
self.ua, self.fluid, self.water_temp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl PyCondenser {
|
||||
pub(crate) fn build(&self) -> Box<dyn Component> {
|
||||
Box::new(entropyk::Condenser::new(self.ua))
|
||||
Box::new(entropyk_components::PyHeatExchangerReal::condenser(
|
||||
self.ua,
|
||||
&self.fluid,
|
||||
self.water_temp,
|
||||
self.water_flow,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Evaporator
|
||||
// Evaporator - Real Heat Exchanger with Water Side
|
||||
// =============================================================================
|
||||
|
||||
/// An evaporator (heat absorption) component.
|
||||
/// An evaporator with water-side heat transfer.
|
||||
///
|
||||
/// Uses ε-NTU method with CoolProp for refrigerant properties.
|
||||
///
|
||||
/// Example::
|
||||
///
|
||||
/// evap = Evaporator(ua=3000.0)
|
||||
/// evap = Evaporator(ua=3000.0, fluid="R134a", water_temp=12.0, water_flow=0.4)
|
||||
#[pyclass(name = "Evaporator", module = "entropyk")]
|
||||
#[derive(Clone)]
|
||||
pub struct PyEvaporator {
|
||||
pub(crate) ua: f64,
|
||||
pub(crate) fluid: String,
|
||||
pub(crate) water_temp: f64,
|
||||
pub(crate) water_flow: f64,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyEvaporator {
|
||||
#[new]
|
||||
#[pyo3(signature = (ua=3000.0))]
|
||||
fn new(ua: f64) -> PyResult<Self> {
|
||||
#[pyo3(signature = (ua=3000.0, fluid="R134a", water_temp=12.0, water_flow=0.4))]
|
||||
fn new(ua: f64, fluid: &str, water_temp: f64, water_flow: f64) -> PyResult<Self> {
|
||||
if ua <= 0.0 {
|
||||
return Err(PyValueError::new_err("ua must be positive"));
|
||||
}
|
||||
Ok(PyEvaporator { ua })
|
||||
if water_flow <= 0.0 {
|
||||
return Err(PyValueError::new_err("water_flow must be positive"));
|
||||
}
|
||||
Ok(PyEvaporator {
|
||||
ua,
|
||||
fluid: fluid.to_string(),
|
||||
water_temp,
|
||||
water_flow,
|
||||
})
|
||||
}
|
||||
|
||||
/// Thermal conductance UA in W/K.
|
||||
@@ -262,13 +227,21 @@ impl PyEvaporator {
|
||||
}
|
||||
|
||||
fn __repr__(&self) -> String {
|
||||
format!("Evaporator(UA={:.1} W/K)", self.ua)
|
||||
format!(
|
||||
"Evaporator(UA={:.1} W/K, fluid={}, water={:.1}°C)",
|
||||
self.ua, self.fluid, self.water_temp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl PyEvaporator {
|
||||
pub(crate) fn build(&self) -> Box<dyn Component> {
|
||||
Box::new(entropyk::Evaporator::new(self.ua))
|
||||
Box::new(entropyk_components::PyHeatExchangerReal::evaporator(
|
||||
self.ua,
|
||||
&self.fluid,
|
||||
self.water_temp,
|
||||
self.water_flow,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,10 +250,6 @@ impl PyEvaporator {
|
||||
// =============================================================================
|
||||
|
||||
/// An economizer (subcooler / internal heat exchanger) component.
|
||||
///
|
||||
/// Example::
|
||||
///
|
||||
/// econ = Economizer(ua=2000.0, effectiveness=0.8)
|
||||
#[pyclass(name = "Economizer", module = "entropyk")]
|
||||
#[derive(Clone)]
|
||||
pub struct PyEconomizer {
|
||||
@@ -305,37 +274,33 @@ impl PyEconomizer {
|
||||
|
||||
impl PyEconomizer {
|
||||
pub(crate) fn build(&self) -> Box<dyn Component> {
|
||||
Box::new(entropyk::Economizer::new(self.ua))
|
||||
Box::new(entropyk_components::Economizer::new(self.ua))
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ExpansionValve
|
||||
// Expansion Valve - Real Isenthalpic
|
||||
// =============================================================================
|
||||
|
||||
/// An expansion valve (isenthalpic throttling device).
|
||||
/// An expansion valve with isenthalpic throttling.
|
||||
///
|
||||
/// Example::
|
||||
///
|
||||
/// valve = ExpansionValve(fluid="R134a", opening=1.0)
|
||||
/// valve = ExpansionValve(fluid="R134a", opening=0.5)
|
||||
#[pyclass(name = "ExpansionValve", module = "entropyk")]
|
||||
#[derive(Clone)]
|
||||
pub struct PyExpansionValve {
|
||||
pub(crate) fluid: String,
|
||||
pub(crate) opening: Option<f64>,
|
||||
pub(crate) opening: f64,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyExpansionValve {
|
||||
#[new]
|
||||
#[pyo3(signature = (fluid="R134a", opening=None))]
|
||||
fn new(fluid: &str, opening: Option<f64>) -> PyResult<Self> {
|
||||
if let Some(o) = opening {
|
||||
if !(0.0..=1.0).contains(&o) {
|
||||
return Err(PyValueError::new_err(
|
||||
"opening must be between 0.0 and 1.0",
|
||||
));
|
||||
}
|
||||
#[pyo3(signature = (fluid="R134a", opening=0.5))]
|
||||
fn new(fluid: &str, opening: f64) -> PyResult<Self> {
|
||||
if !(0.0..=1.0).contains(&opening) {
|
||||
return Err(PyValueError::new_err("opening must be between 0.0 and 1.0"));
|
||||
}
|
||||
Ok(PyExpansionValve {
|
||||
fluid: fluid.to_string(),
|
||||
@@ -349,103 +314,67 @@ impl PyExpansionValve {
|
||||
&self.fluid
|
||||
}
|
||||
|
||||
/// Valve opening (0–1), None if fully open.
|
||||
/// Valve opening (0–1).
|
||||
#[getter]
|
||||
fn opening_value(&self) -> Option<f64> {
|
||||
fn opening_value(&self) -> f64 {
|
||||
self.opening
|
||||
}
|
||||
|
||||
fn __repr__(&self) -> String {
|
||||
match self.opening {
|
||||
Some(o) => format!("ExpansionValve(fluid={}, opening={:.2})", self.fluid, o),
|
||||
None => format!("ExpansionValve(fluid={})", self.fluid),
|
||||
}
|
||||
format!(
|
||||
"ExpansionValve(fluid={}, opening={:.2})",
|
||||
self.fluid, self.opening
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl PyExpansionValve {
|
||||
pub(crate) fn build(&self) -> Box<dyn Component> {
|
||||
// ExpansionValve uses type-state pattern; 2 equations
|
||||
Box::new(SimpleAdapter::new("ExpansionValve", 2))
|
||||
Box::new(entropyk_components::PyExpansionValveReal::new(
|
||||
&self.fluid,
|
||||
self.opening,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Pipe
|
||||
// Pipe - Real Pressure Drop
|
||||
// =============================================================================
|
||||
|
||||
/// A pipe component with pressure drop (Darcy-Weisbach).
|
||||
///
|
||||
/// Example::
|
||||
///
|
||||
/// pipe = Pipe(length=10.0, diameter=0.05, fluid="R134a",
|
||||
/// density=1140.0, viscosity=0.0002)
|
||||
/// A pipe component with Darcy-Weisbach pressure drop.
|
||||
#[pyclass(name = "Pipe", module = "entropyk")]
|
||||
#[derive(Clone)]
|
||||
#[allow(dead_code)] // Fields reserved until SimpleAdapter type-state migration
|
||||
pub struct PyPipe {
|
||||
pub(crate) length: f64,
|
||||
pub(crate) diameter: f64,
|
||||
pub(crate) roughness: f64,
|
||||
pub(crate) fluid: String,
|
||||
pub(crate) density: f64,
|
||||
pub(crate) viscosity: f64,
|
||||
pub(crate) inner: entropyk_components::PyPipeReal,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyPipe {
|
||||
#[new]
|
||||
#[pyo3(signature = (
|
||||
length=10.0,
|
||||
diameter=0.05,
|
||||
fluid="R134a",
|
||||
density=1140.0,
|
||||
viscosity=0.0002,
|
||||
roughness=0.0000015
|
||||
))]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new(
|
||||
length: f64,
|
||||
diameter: f64,
|
||||
fluid: &str,
|
||||
density: f64,
|
||||
viscosity: f64,
|
||||
roughness: f64,
|
||||
) -> PyResult<Self> {
|
||||
#[pyo3(signature = (length=10.0, diameter=0.05, fluid="R134a"))]
|
||||
fn new(length: f64, diameter: f64, fluid: &str) -> PyResult<Self> {
|
||||
if length <= 0.0 {
|
||||
return Err(PyValueError::new_err("length must be positive"));
|
||||
}
|
||||
if diameter <= 0.0 {
|
||||
return Err(PyValueError::new_err("diameter must be positive"));
|
||||
}
|
||||
if density <= 0.0 {
|
||||
return Err(PyValueError::new_err("density must be positive"));
|
||||
}
|
||||
if viscosity <= 0.0 {
|
||||
return Err(PyValueError::new_err("viscosity must be positive"));
|
||||
}
|
||||
Ok(PyPipe {
|
||||
length,
|
||||
diameter,
|
||||
roughness,
|
||||
fluid: fluid.to_string(),
|
||||
density,
|
||||
viscosity,
|
||||
inner: entropyk_components::PyPipeReal::new(length, diameter, fluid),
|
||||
})
|
||||
}
|
||||
|
||||
fn __repr__(&self) -> String {
|
||||
format!(
|
||||
"Pipe(L={:.2}m, D={:.4}m, fluid={})",
|
||||
self.length, self.diameter, self.fluid
|
||||
self.inner.length, self.inner.diameter, self.inner.fluid.0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl PyPipe {
|
||||
pub(crate) fn build(&self) -> Box<dyn Component> {
|
||||
// Pipe uses type-state pattern; 1 equation (pressure drop)
|
||||
Box::new(SimpleAdapter::new("Pipe", 1))
|
||||
Box::new(self.inner.clone())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -454,10 +383,6 @@ impl PyPipe {
|
||||
// =============================================================================
|
||||
|
||||
/// A pump component for liquid flow.
|
||||
///
|
||||
/// Example::
|
||||
///
|
||||
/// pump = Pump(pressure_rise_pa=200000.0, efficiency=0.75)
|
||||
#[pyclass(name = "Pump", module = "entropyk")]
|
||||
#[derive(Clone)]
|
||||
pub struct PyPump {
|
||||
@@ -494,7 +419,7 @@ impl PyPump {
|
||||
|
||||
impl PyPump {
|
||||
pub(crate) fn build(&self) -> Box<dyn Component> {
|
||||
Box::new(SimpleAdapter::new("Pump", 2))
|
||||
Box::new(entropyk_components::PyPipeReal::new(1.0, 0.05, "Water"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -503,10 +428,6 @@ impl PyPump {
|
||||
// =============================================================================
|
||||
|
||||
/// A fan component for air flow.
|
||||
///
|
||||
/// Example::
|
||||
///
|
||||
/// fan = Fan(pressure_rise_pa=500.0, efficiency=0.65)
|
||||
#[pyclass(name = "Fan", module = "entropyk")]
|
||||
#[derive(Clone)]
|
||||
pub struct PyFan {
|
||||
@@ -543,7 +464,7 @@ impl PyFan {
|
||||
|
||||
impl PyFan {
|
||||
pub(crate) fn build(&self) -> Box<dyn Component> {
|
||||
Box::new(SimpleAdapter::new("Fan", 2))
|
||||
Box::new(entropyk_components::PyPipeReal::new(1.0, 0.1, "Air"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -551,11 +472,7 @@ impl PyFan {
|
||||
// FlowSplitter
|
||||
// =============================================================================
|
||||
|
||||
/// A flow splitter that divides a stream into two or more branches.
|
||||
///
|
||||
/// Example::
|
||||
///
|
||||
/// splitter = FlowSplitter(n_outlets=2)
|
||||
/// A flow splitter that divides a stream into branches.
|
||||
#[pyclass(name = "FlowSplitter", module = "entropyk")]
|
||||
#[derive(Clone)]
|
||||
pub struct PyFlowSplitter {
|
||||
@@ -580,7 +497,7 @@ impl PyFlowSplitter {
|
||||
|
||||
impl PyFlowSplitter {
|
||||
pub(crate) fn build(&self) -> Box<dyn Component> {
|
||||
Box::new(SimpleAdapter::new("FlowSplitter", self.n_outlets))
|
||||
Box::new(entropyk_components::PyFlowSplitterReal::new(self.n_outlets))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -588,11 +505,7 @@ impl PyFlowSplitter {
|
||||
// FlowMerger
|
||||
// =============================================================================
|
||||
|
||||
/// A flow merger that combines two or more branches into one.
|
||||
///
|
||||
/// Example::
|
||||
///
|
||||
/// merger = FlowMerger(n_inlets=2)
|
||||
/// A flow merger that combines branches into one.
|
||||
#[pyclass(name = "FlowMerger", module = "entropyk")]
|
||||
#[derive(Clone)]
|
||||
pub struct PyFlowMerger {
|
||||
@@ -617,31 +530,32 @@ impl PyFlowMerger {
|
||||
|
||||
impl PyFlowMerger {
|
||||
pub(crate) fn build(&self) -> Box<dyn Component> {
|
||||
Box::new(SimpleAdapter::new("FlowMerger", self.n_inlets))
|
||||
Box::new(entropyk_components::PyFlowMergerReal::new(self.n_inlets))
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// FlowSource
|
||||
// FlowSource - Real Boundary Condition
|
||||
// =============================================================================
|
||||
|
||||
/// A boundary condition representing a mass flow source.
|
||||
///
|
||||
/// Example::
|
||||
///
|
||||
/// source = FlowSource(pressure_pa=101325.0, temperature_k=300.0)
|
||||
/// source = FlowSource(pressure_pa=101325.0, temperature_k=300.0, fluid="Water")
|
||||
#[pyclass(name = "FlowSource", module = "entropyk")]
|
||||
#[derive(Clone)]
|
||||
pub struct PyFlowSource {
|
||||
pub(crate) pressure_pa: f64,
|
||||
pub(crate) temperature_k: f64,
|
||||
pub(crate) fluid: String,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyFlowSource {
|
||||
#[new]
|
||||
#[pyo3(signature = (pressure_pa=101325.0, temperature_k=300.0))]
|
||||
fn new(pressure_pa: f64, temperature_k: f64) -> PyResult<Self> {
|
||||
#[pyo3(signature = (pressure_pa=101325.0, temperature_k=300.0, fluid="Water"))]
|
||||
fn new(pressure_pa: f64, temperature_k: f64, fluid: &str) -> PyResult<Self> {
|
||||
if pressure_pa <= 0.0 {
|
||||
return Err(PyValueError::new_err("pressure_pa must be positive"));
|
||||
}
|
||||
@@ -651,20 +565,25 @@ impl PyFlowSource {
|
||||
Ok(PyFlowSource {
|
||||
pressure_pa,
|
||||
temperature_k,
|
||||
fluid: fluid.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
fn __repr__(&self) -> String {
|
||||
format!(
|
||||
"FlowSource(P={:.0} Pa, T={:.1} K)",
|
||||
self.pressure_pa, self.temperature_k
|
||||
"FlowSource(P={:.0} Pa, T={:.1} K, fluid={})",
|
||||
self.pressure_pa, self.temperature_k, self.fluid
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl PyFlowSource {
|
||||
pub(crate) fn build(&self) -> Box<dyn Component> {
|
||||
Box::new(SimpleAdapter::new("FlowSource", 0))
|
||||
Box::new(entropyk_components::PyFlowSourceReal::new(
|
||||
&self.fluid,
|
||||
self.pressure_pa,
|
||||
self.temperature_k,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -673,12 +592,9 @@ impl PyFlowSource {
|
||||
// =============================================================================
|
||||
|
||||
/// A boundary condition representing a mass flow sink.
|
||||
///
|
||||
/// Example::
|
||||
///
|
||||
/// sink = FlowSink()
|
||||
#[pyclass(name = "FlowSink", module = "entropyk")]
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Default)]
|
||||
|
||||
pub struct PyFlowSink;
|
||||
|
||||
#[pymethods]
|
||||
@@ -695,7 +611,7 @@ impl PyFlowSink {
|
||||
|
||||
impl PyFlowSink {
|
||||
pub(crate) fn build(&self) -> Box<dyn Component> {
|
||||
Box::new(SimpleAdapter::new("FlowSink", 0))
|
||||
Box::new(entropyk_components::PyFlowSinkReal::default())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -707,7 +623,7 @@ impl PyFlowSink {
|
||||
#[pyclass(name = "OperationalState", module = "entropyk")]
|
||||
#[derive(Clone)]
|
||||
pub struct PyOperationalState {
|
||||
pub(crate) inner: entropyk::OperationalState,
|
||||
pub(crate) inner: entropyk_components::OperationalState,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
@@ -716,9 +632,9 @@ impl PyOperationalState {
|
||||
#[new]
|
||||
fn new(state: &str) -> PyResult<Self> {
|
||||
let inner = match state.to_lowercase().as_str() {
|
||||
"on" => entropyk::OperationalState::On,
|
||||
"off" => entropyk::OperationalState::Off,
|
||||
"bypass" => entropyk::OperationalState::Bypass,
|
||||
"on" => entropyk_components::OperationalState::On,
|
||||
"off" => entropyk_components::OperationalState::Off,
|
||||
"bypass" => entropyk_components::OperationalState::Bypass,
|
||||
_ => {
|
||||
return Err(PyValueError::new_err(
|
||||
"state must be one of: 'on', 'off', 'bypass'",
|
||||
|
||||
@@ -14,21 +14,64 @@ use pyo3::prelude::*;
|
||||
// ├── TopologyError
|
||||
// └── ValidationError
|
||||
|
||||
create_exception!(entropyk, EntropykError, PyException, "Base exception for all Entropyk errors.");
|
||||
create_exception!(entropyk, SolverError, EntropykError, "Error during solving (non-convergence, divergence).");
|
||||
create_exception!(entropyk, TimeoutError, SolverError, "Solver timed out before convergence.");
|
||||
create_exception!(entropyk, ControlSaturationError, SolverError, "Control variable reached saturation limit.");
|
||||
create_exception!(entropyk, FluidError, EntropykError, "Error during fluid property calculation.");
|
||||
create_exception!(entropyk, ComponentError, EntropykError, "Error from component operations.");
|
||||
create_exception!(entropyk, TopologyError, EntropykError, "Error in system topology (graph structure).");
|
||||
create_exception!(entropyk, ValidationError, EntropykError, "Validation error (calibration, constraints).");
|
||||
create_exception!(
|
||||
entropyk,
|
||||
EntropykError,
|
||||
PyException,
|
||||
"Base exception for all Entropyk errors."
|
||||
);
|
||||
create_exception!(
|
||||
entropyk,
|
||||
SolverError,
|
||||
EntropykError,
|
||||
"Error during solving (non-convergence, divergence)."
|
||||
);
|
||||
create_exception!(
|
||||
entropyk,
|
||||
TimeoutError,
|
||||
SolverError,
|
||||
"Solver timed out before convergence."
|
||||
);
|
||||
create_exception!(
|
||||
entropyk,
|
||||
ControlSaturationError,
|
||||
SolverError,
|
||||
"Control variable reached saturation limit."
|
||||
);
|
||||
create_exception!(
|
||||
entropyk,
|
||||
FluidError,
|
||||
EntropykError,
|
||||
"Error during fluid property calculation."
|
||||
);
|
||||
create_exception!(
|
||||
entropyk,
|
||||
ComponentError,
|
||||
EntropykError,
|
||||
"Error from component operations."
|
||||
);
|
||||
create_exception!(
|
||||
entropyk,
|
||||
TopologyError,
|
||||
EntropykError,
|
||||
"Error in system topology (graph structure)."
|
||||
);
|
||||
create_exception!(
|
||||
entropyk,
|
||||
ValidationError,
|
||||
EntropykError,
|
||||
"Validation error (calibration, constraints)."
|
||||
);
|
||||
|
||||
/// Registers all exception types in the Python module.
|
||||
pub fn register_exceptions(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add("EntropykError", m.py().get_type::<EntropykError>())?;
|
||||
m.add("SolverError", m.py().get_type::<SolverError>())?;
|
||||
m.add("TimeoutError", m.py().get_type::<TimeoutError>())?;
|
||||
m.add("ControlSaturationError", m.py().get_type::<ControlSaturationError>())?;
|
||||
m.add(
|
||||
"ControlSaturationError",
|
||||
m.py().get_type::<ControlSaturationError>(),
|
||||
)?;
|
||||
m.add("FluidError", m.py().get_type::<FluidError>())?;
|
||||
m.add("ComponentError", m.py().get_type::<ComponentError>())?;
|
||||
m.add("TopologyError", m.py().get_type::<TopologyError>())?;
|
||||
@@ -65,12 +108,13 @@ pub fn thermo_error_to_pyerr(err: entropyk::ThermoError) -> PyErr {
|
||||
ThermoError::Calibration(_) | ThermoError::Constraint(_) => {
|
||||
ValidationError::new_err(err.to_string())
|
||||
}
|
||||
// Map Validation errors (mass/energy balance violations) to ValidationError
|
||||
ThermoError::Validation { .. } => ValidationError::new_err(err.to_string()),
|
||||
ThermoError::Initialization(_)
|
||||
| ThermoError::Builder(_)
|
||||
| ThermoError::Mixture(_)
|
||||
| ThermoError::InvalidInput(_)
|
||||
| ThermoError::NotSupported(_)
|
||||
| ThermoError::NotFinalized
|
||||
| ThermoError::Validation { .. } => EntropykError::new_err(err.to_string()),
|
||||
| ThermoError::NotFinalized => EntropykError::new_err(err.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,10 @@ fn entropyk(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_class::<solver::PyConvergenceStatus>()?;
|
||||
m.add_class::<solver::PyConstraint>()?;
|
||||
m.add_class::<solver::PyBoundedVariable>()?;
|
||||
m.add_class::<solver::PyConvergenceCriteria>()?;
|
||||
m.add_class::<solver::PyJacobianFreezingConfig>()?;
|
||||
m.add_class::<solver::PyTimeoutConfig>()?;
|
||||
m.add_class::<solver::PySolverStrategy>()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use pyo3::exceptions::{PyRuntimeError, PyValueError};
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::exceptions::{PyValueError, PyRuntimeError};
|
||||
use std::time::Duration;
|
||||
use std::panic;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::components::AnyPyComponent;
|
||||
|
||||
@@ -44,7 +44,8 @@ impl PyConstraint {
|
||||
ComponentOutput::Superheat { component_id },
|
||||
target_value,
|
||||
tolerance,
|
||||
).unwrap(),
|
||||
)
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +59,8 @@ impl PyConstraint {
|
||||
ComponentOutput::Subcooling { component_id },
|
||||
target_value,
|
||||
tolerance,
|
||||
).unwrap(),
|
||||
)
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,12 +74,18 @@ impl PyConstraint {
|
||||
ComponentOutput::Capacity { component_id },
|
||||
target_value,
|
||||
tolerance,
|
||||
).unwrap(),
|
||||
)
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
fn __repr__(&self) -> String {
|
||||
format!("Constraint(id='{}', target={}, tol={})", self.inner.id(), self.inner.target_value(), self.inner.tolerance())
|
||||
format!(
|
||||
"Constraint(id='{}', target={}, tol={})",
|
||||
self.inner.id(),
|
||||
self.inner.target_value(),
|
||||
self.inner.tolerance()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,10 +99,18 @@ pub struct PyBoundedVariable {
|
||||
impl PyBoundedVariable {
|
||||
#[new]
|
||||
#[pyo3(signature = (id, value, min, max, component_id=None))]
|
||||
fn new(id: String, value: f64, min: f64, max: f64, component_id: Option<String>) -> PyResult<Self> {
|
||||
fn new(
|
||||
id: String,
|
||||
value: f64,
|
||||
min: f64,
|
||||
max: f64,
|
||||
component_id: Option<String>,
|
||||
) -> PyResult<Self> {
|
||||
use entropyk_solver::inverse::{BoundedVariable, BoundedVariableId};
|
||||
let inner = match component_id {
|
||||
Some(cid) => BoundedVariable::with_component(BoundedVariableId::new(id), cid, value, min, max),
|
||||
Some(cid) => {
|
||||
BoundedVariable::with_component(BoundedVariableId::new(id), cid, value, min, max)
|
||||
}
|
||||
None => BoundedVariable::new(BoundedVariableId::new(id), value, min, max),
|
||||
};
|
||||
match inner {
|
||||
@@ -105,7 +121,13 @@ impl PyBoundedVariable {
|
||||
|
||||
fn __repr__(&self) -> String {
|
||||
// use is_saturated if available but simpler:
|
||||
format!("BoundedVariable(id='{}', value={}, bounds=[{}, {}])", self.inner.id(), self.inner.value(), self.inner.min(), self.inner.max())
|
||||
format!(
|
||||
"BoundedVariable(id='{}', value={}, bounds=[{}, {}])",
|
||||
self.inner.id(),
|
||||
self.inner.value(),
|
||||
self.inner.min(),
|
||||
self.inner.max()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,23 +181,31 @@ impl PySystem {
|
||||
|
||||
/// Add a constraint to the system.
|
||||
fn add_constraint(&mut self, constraint: &PyConstraint) -> PyResult<()> {
|
||||
self.inner.add_constraint(constraint.inner.clone())
|
||||
self.inner
|
||||
.add_constraint(constraint.inner.clone())
|
||||
.map_err(|e| PyValueError::new_err(e.to_string()))
|
||||
}
|
||||
|
||||
/// Add a bounded variable to the system.
|
||||
fn add_bounded_variable(&mut self, variable: &PyBoundedVariable) -> PyResult<()> {
|
||||
self.inner.add_bounded_variable(variable.inner.clone())
|
||||
self.inner
|
||||
.add_bounded_variable(variable.inner.clone())
|
||||
.map_err(|e| PyValueError::new_err(e.to_string()))
|
||||
}
|
||||
|
||||
/// Link a constraint to a control variable for the inverse solver.
|
||||
fn link_constraint_to_control(&mut self, constraint_id: &str, control_id: &str) -> PyResult<()> {
|
||||
use entropyk_solver::inverse::{ConstraintId, BoundedVariableId};
|
||||
self.inner.link_constraint_to_control(
|
||||
&ConstraintId::new(constraint_id),
|
||||
&BoundedVariableId::new(control_id),
|
||||
).map_err(|e| PyValueError::new_err(e.to_string()))
|
||||
fn link_constraint_to_control(
|
||||
&mut self,
|
||||
constraint_id: &str,
|
||||
control_id: &str,
|
||||
) -> PyResult<()> {
|
||||
use entropyk_solver::inverse::{BoundedVariableId, ConstraintId};
|
||||
self.inner
|
||||
.link_constraint_to_control(
|
||||
&ConstraintId::new(constraint_id),
|
||||
&BoundedVariableId::new(control_id),
|
||||
)
|
||||
.map_err(|e| PyValueError::new_err(e.to_string()))
|
||||
}
|
||||
|
||||
/// Finalize the system graph: build state index mapping and validate topology.
|
||||
@@ -261,13 +291,136 @@ fn extract_component(obj: &Bound<'_, PyAny>) -> PyResult<AnyPyComponent> {
|
||||
fn solver_error_to_pyerr(err: entropyk_solver::SolverError) -> PyErr {
|
||||
let msg = err.to_string();
|
||||
match &err {
|
||||
entropyk_solver::SolverError::Timeout { .. } => {
|
||||
crate::errors::TimeoutError::new_err(msg)
|
||||
entropyk_solver::SolverError::Timeout { .. } => crate::errors::TimeoutError::new_err(msg),
|
||||
_ => crate::errors::SolverError::new_err(msg),
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Supporting Types (Story 6.6)
|
||||
// =============================================================================
|
||||
|
||||
#[pyclass(name = "ConvergenceCriteria", module = "entropyk")]
|
||||
#[derive(Clone, Default)]
|
||||
pub struct PyConvergenceCriteria {
|
||||
pub(crate) inner: entropyk_solver::criteria::ConvergenceCriteria,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyConvergenceCriteria {
|
||||
#[new]
|
||||
#[pyo3(signature = (pressure_tolerance_pa=1.0, mass_balance_tolerance_kgs=1e-9, energy_balance_tolerance_w=1e-3))]
|
||||
fn new(
|
||||
pressure_tolerance_pa: f64,
|
||||
mass_balance_tolerance_kgs: f64,
|
||||
energy_balance_tolerance_w: f64,
|
||||
) -> Self {
|
||||
PyConvergenceCriteria {
|
||||
inner: entropyk_solver::criteria::ConvergenceCriteria {
|
||||
pressure_tolerance_pa,
|
||||
mass_balance_tolerance_kgs,
|
||||
energy_balance_tolerance_w,
|
||||
},
|
||||
}
|
||||
_ => {
|
||||
crate::errors::SolverError::new_err(msg)
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn pressure_tolerance_pa(&self) -> f64 {
|
||||
self.inner.pressure_tolerance_pa
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn mass_balance_tolerance_kgs(&self) -> f64 {
|
||||
self.inner.mass_balance_tolerance_kgs
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn energy_balance_tolerance_w(&self) -> f64 {
|
||||
self.inner.energy_balance_tolerance_w
|
||||
}
|
||||
|
||||
fn __repr__(&self) -> String {
|
||||
format!(
|
||||
"ConvergenceCriteria(dP={:.1e} Pa, dM={:.1e} kg/s, dE={:.1e} W)",
|
||||
self.inner.pressure_tolerance_pa,
|
||||
self.inner.mass_balance_tolerance_kgs,
|
||||
self.inner.energy_balance_tolerance_w
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(name = "JacobianFreezingConfig", module = "entropyk")]
|
||||
#[derive(Clone, Default)]
|
||||
pub struct PyJacobianFreezingConfig {
|
||||
pub(crate) inner: entropyk_solver::solver::JacobianFreezingConfig,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyJacobianFreezingConfig {
|
||||
#[new]
|
||||
#[pyo3(signature = (max_frozen_iters=3, threshold=0.1))]
|
||||
fn new(max_frozen_iters: usize, threshold: f64) -> Self {
|
||||
PyJacobianFreezingConfig {
|
||||
inner: entropyk_solver::solver::JacobianFreezingConfig {
|
||||
max_frozen_iters,
|
||||
threshold,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn max_frozen_iters(&self) -> usize {
|
||||
self.inner.max_frozen_iters
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn threshold(&self) -> f64 {
|
||||
self.inner.threshold
|
||||
}
|
||||
|
||||
fn __repr__(&self) -> String {
|
||||
format!(
|
||||
"JacobianFreezingConfig(max_iters={}, threshold={:.2})",
|
||||
self.inner.max_frozen_iters, self.inner.threshold
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(name = "TimeoutConfig", module = "entropyk")]
|
||||
#[derive(Clone, Default)]
|
||||
pub struct PyTimeoutConfig {
|
||||
pub(crate) inner: entropyk_solver::solver::TimeoutConfig,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyTimeoutConfig {
|
||||
#[new]
|
||||
#[pyo3(signature = (return_best_state_on_timeout=true, zoh_fallback=false))]
|
||||
fn new(return_best_state_on_timeout: bool, zoh_fallback: bool) -> Self {
|
||||
PyTimeoutConfig {
|
||||
inner: entropyk_solver::solver::TimeoutConfig {
|
||||
return_best_state_on_timeout,
|
||||
zoh_fallback,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn return_best_state_on_timeout(&self) -> bool {
|
||||
self.inner.return_best_state_on_timeout
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn zoh_fallback(&self) -> bool {
|
||||
self.inner.zoh_fallback
|
||||
}
|
||||
|
||||
fn __repr__(&self) -> String {
|
||||
format!(
|
||||
"TimeoutConfig(return_best={}, zoh={})",
|
||||
self.inner.return_best_state_on_timeout, self.inner.zoh_fallback
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -281,30 +434,90 @@ fn solver_error_to_pyerr(err: entropyk_solver::SolverError) -> PyErr {
|
||||
/// config = NewtonConfig(max_iterations=100, tolerance=1e-6)
|
||||
/// result = config.solve(system)
|
||||
#[pyclass(name = "NewtonConfig", module = "entropyk")]
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Default)]
|
||||
pub struct PyNewtonConfig {
|
||||
#[pyo3(get, set)]
|
||||
pub(crate) max_iterations: usize,
|
||||
#[pyo3(get, set)]
|
||||
pub(crate) tolerance: f64,
|
||||
#[pyo3(get, set)]
|
||||
pub(crate) line_search: bool,
|
||||
#[pyo3(get, set)]
|
||||
pub(crate) timeout_ms: Option<u64>,
|
||||
#[pyo3(get, set)]
|
||||
pub(crate) initial_state: Option<Vec<f64>>,
|
||||
#[pyo3(get, set)]
|
||||
pub(crate) use_numerical_jacobian: bool,
|
||||
#[pyo3(get, set)]
|
||||
pub(crate) jacobian_freezing: Option<PyJacobianFreezingConfig>,
|
||||
#[pyo3(get, set)]
|
||||
pub(crate) convergence_criteria: Option<PyConvergenceCriteria>,
|
||||
#[pyo3(get, set)]
|
||||
pub(crate) timeout_config: PyTimeoutConfig,
|
||||
#[pyo3(get, set)]
|
||||
pub(crate) previous_state: Option<Vec<f64>>,
|
||||
#[pyo3(get, set)]
|
||||
pub(crate) line_search_armijo_c: f64,
|
||||
#[pyo3(get, set)]
|
||||
pub(crate) line_search_max_backtracks: usize,
|
||||
#[pyo3(get, set)]
|
||||
pub(crate) divergence_threshold: f64,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyNewtonConfig {
|
||||
#[new]
|
||||
#[pyo3(signature = (max_iterations=100, tolerance=1e-6, line_search=false, timeout_ms=None))]
|
||||
#[pyo3(signature = (
|
||||
max_iterations=100,
|
||||
tolerance=1e-6,
|
||||
line_search=false,
|
||||
timeout_ms=None,
|
||||
initial_state=None,
|
||||
use_numerical_jacobian=false,
|
||||
jacobian_freezing=None,
|
||||
convergence_criteria=None,
|
||||
timeout_config=None,
|
||||
previous_state=None,
|
||||
line_search_armijo_c=1e-4,
|
||||
line_search_max_backtracks=20,
|
||||
divergence_threshold=1e10
|
||||
))]
|
||||
fn new(
|
||||
max_iterations: usize,
|
||||
tolerance: f64,
|
||||
line_search: bool,
|
||||
timeout_ms: Option<u64>,
|
||||
) -> Self {
|
||||
PyNewtonConfig {
|
||||
initial_state: Option<Vec<f64>>,
|
||||
use_numerical_jacobian: bool,
|
||||
jacobian_freezing: Option<PyJacobianFreezingConfig>,
|
||||
convergence_criteria: Option<PyConvergenceCriteria>,
|
||||
timeout_config: Option<PyTimeoutConfig>,
|
||||
previous_state: Option<Vec<f64>>,
|
||||
line_search_armijo_c: f64,
|
||||
line_search_max_backtracks: usize,
|
||||
divergence_threshold: f64,
|
||||
) -> PyResult<Self> {
|
||||
if tolerance <= 0.0 {
|
||||
return Err(PyValueError::new_err("tolerance must be greater than 0"));
|
||||
}
|
||||
if divergence_threshold <= tolerance {
|
||||
return Err(PyValueError::new_err("divergence_threshold must be greater than tolerance"));
|
||||
}
|
||||
Ok(PyNewtonConfig {
|
||||
max_iterations,
|
||||
tolerance,
|
||||
line_search,
|
||||
timeout_ms,
|
||||
}
|
||||
initial_state,
|
||||
use_numerical_jacobian,
|
||||
jacobian_freezing,
|
||||
convergence_criteria,
|
||||
timeout_config: timeout_config.unwrap_or_default(),
|
||||
previous_state,
|
||||
line_search_armijo_c,
|
||||
line_search_max_backtracks,
|
||||
divergence_threshold,
|
||||
})
|
||||
}
|
||||
|
||||
/// Solve the system. Returns a ConvergedState on success.
|
||||
@@ -326,14 +539,22 @@ impl PyNewtonConfig {
|
||||
tolerance: self.tolerance,
|
||||
line_search: self.line_search,
|
||||
timeout: self.timeout_ms.map(Duration::from_millis),
|
||||
..Default::default()
|
||||
use_numerical_jacobian: self.use_numerical_jacobian,
|
||||
line_search_armijo_c: self.line_search_armijo_c,
|
||||
line_search_max_backtracks: self.line_search_max_backtracks,
|
||||
divergence_threshold: self.divergence_threshold,
|
||||
timeout_config: self.timeout_config.inner.clone(),
|
||||
previous_state: self.previous_state.clone(),
|
||||
previous_residual: None,
|
||||
initial_state: self.initial_state.clone(),
|
||||
convergence_criteria: self.convergence_criteria.as_ref().map(|cc| cc.inner.clone()),
|
||||
jacobian_freezing: self.jacobian_freezing.as_ref().map(|jf| jf.inner.clone()),
|
||||
};
|
||||
|
||||
// Catch any Rust panic to prevent it from reaching Python (Task 5.4)
|
||||
use entropyk_solver::Solver;
|
||||
let solve_result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
|
||||
config.solve(&mut system.inner)
|
||||
}));
|
||||
let solve_result =
|
||||
panic::catch_unwind(panic::AssertUnwindSafe(|| config.solve(&mut system.inner)));
|
||||
|
||||
match solve_result {
|
||||
Ok(Ok(converged)) => Ok(PyConvergedState::from_rust(converged)),
|
||||
@@ -363,18 +584,44 @@ impl PyNewtonConfig {
|
||||
/// config = PicardConfig(max_iterations=500, tolerance=1e-4)
|
||||
/// result = config.solve(system)
|
||||
#[pyclass(name = "PicardConfig", module = "entropyk")]
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Default)]
|
||||
pub struct PyPicardConfig {
|
||||
#[pyo3(get, set)]
|
||||
pub(crate) max_iterations: usize,
|
||||
#[pyo3(get, set)]
|
||||
pub(crate) tolerance: f64,
|
||||
#[pyo3(get, set)]
|
||||
pub(crate) relaxation: f64,
|
||||
#[pyo3(get, set)]
|
||||
pub(crate) initial_state: Option<Vec<f64>>,
|
||||
#[pyo3(get, set)]
|
||||
pub(crate) timeout_ms: Option<u64>,
|
||||
#[pyo3(get, set)]
|
||||
pub(crate) convergence_criteria: Option<PyConvergenceCriteria>,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyPicardConfig {
|
||||
#[new]
|
||||
#[pyo3(signature = (max_iterations=500, tolerance=1e-4, relaxation=0.5))]
|
||||
fn new(max_iterations: usize, tolerance: f64, relaxation: f64) -> PyResult<Self> {
|
||||
#[pyo3(signature = (
|
||||
max_iterations=500,
|
||||
tolerance=1e-4,
|
||||
relaxation=0.5,
|
||||
initial_state=None,
|
||||
timeout_ms=None,
|
||||
convergence_criteria=None
|
||||
))]
|
||||
fn new(
|
||||
max_iterations: usize,
|
||||
tolerance: f64,
|
||||
relaxation: f64,
|
||||
initial_state: Option<Vec<f64>>,
|
||||
timeout_ms: Option<u64>,
|
||||
convergence_criteria: Option<PyConvergenceCriteria>,
|
||||
) -> PyResult<Self> {
|
||||
if tolerance <= 0.0 {
|
||||
return Err(PyValueError::new_err("tolerance must be greater than 0"));
|
||||
}
|
||||
if !(0.0..=1.0).contains(&relaxation) {
|
||||
return Err(PyValueError::new_err(
|
||||
"relaxation must be between 0.0 and 1.0",
|
||||
@@ -384,6 +631,9 @@ impl PyPicardConfig {
|
||||
max_iterations,
|
||||
tolerance,
|
||||
relaxation,
|
||||
initial_state,
|
||||
timeout_ms,
|
||||
convergence_criteria,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -404,13 +654,15 @@ impl PyPicardConfig {
|
||||
max_iterations: self.max_iterations,
|
||||
tolerance: self.tolerance,
|
||||
relaxation_factor: self.relaxation,
|
||||
timeout: self.timeout_ms.map(Duration::from_millis),
|
||||
initial_state: self.initial_state.clone(),
|
||||
convergence_criteria: self.convergence_criteria.as_ref().map(|cc| cc.inner.clone()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
use entropyk_solver::Solver;
|
||||
let solve_result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
|
||||
config.solve(&mut system.inner)
|
||||
}));
|
||||
let solve_result =
|
||||
panic::catch_unwind(panic::AssertUnwindSafe(|| config.solve(&mut system.inner)));
|
||||
|
||||
match solve_result {
|
||||
Ok(Ok(converged)) => Ok(PyConvergedState::from_rust(converged)),
|
||||
@@ -454,8 +706,8 @@ impl PyFallbackConfig {
|
||||
#[pyo3(signature = (newton=None, picard=None))]
|
||||
fn new(newton: Option<PyNewtonConfig>, picard: Option<PyPicardConfig>) -> PyResult<Self> {
|
||||
Ok(PyFallbackConfig {
|
||||
newton: newton.unwrap_or_else(|| PyNewtonConfig::new(100, 1e-6, false, None)),
|
||||
picard: picard.unwrap_or_else(|| PyPicardConfig::new(500, 1e-4, 0.5).unwrap()),
|
||||
newton: newton.unwrap_or_else(|| PyNewtonConfig::new(100, 1e-6, false, None, None, false, None, None, None, None, 1e-4, 20, 1e10).unwrap()),
|
||||
picard: picard.unwrap_or_else(|| PyPicardConfig::new(500, 1e-4, 0.5, None, None, None).unwrap()),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -477,19 +729,30 @@ impl PyFallbackConfig {
|
||||
tolerance: self.newton.tolerance,
|
||||
line_search: self.newton.line_search,
|
||||
timeout: self.newton.timeout_ms.map(Duration::from_millis),
|
||||
..Default::default()
|
||||
use_numerical_jacobian: self.newton.use_numerical_jacobian,
|
||||
line_search_armijo_c: self.newton.line_search_armijo_c,
|
||||
line_search_max_backtracks: self.newton.line_search_max_backtracks,
|
||||
divergence_threshold: self.newton.divergence_threshold,
|
||||
timeout_config: self.newton.timeout_config.inner.clone(),
|
||||
previous_state: self.newton.previous_state.clone(),
|
||||
previous_residual: None,
|
||||
initial_state: self.newton.initial_state.clone(),
|
||||
convergence_criteria: self.newton.convergence_criteria.as_ref().map(|cc| cc.inner.clone()),
|
||||
jacobian_freezing: self.newton.jacobian_freezing.as_ref().map(|jf| jf.inner.clone()),
|
||||
};
|
||||
let picard_config = entropyk_solver::PicardConfig {
|
||||
max_iterations: self.picard.max_iterations,
|
||||
tolerance: self.picard.tolerance,
|
||||
relaxation_factor: self.picard.relaxation,
|
||||
timeout: self.picard.timeout_ms.map(Duration::from_millis),
|
||||
initial_state: self.picard.initial_state.clone(),
|
||||
convergence_criteria: self.picard.convergence_criteria.as_ref().map(|cc| cc.inner.clone()),
|
||||
..Default::default()
|
||||
};
|
||||
let mut fallback = entropyk_solver::FallbackSolver::new(
|
||||
entropyk_solver::FallbackConfig::default(),
|
||||
)
|
||||
.with_newton_config(newton_config)
|
||||
.with_picard_config(picard_config);
|
||||
let mut fallback =
|
||||
entropyk_solver::FallbackSolver::new(entropyk_solver::FallbackConfig::default())
|
||||
.with_newton_config(newton_config)
|
||||
.with_picard_config(picard_config);
|
||||
|
||||
use entropyk_solver::Solver;
|
||||
let solve_result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
|
||||
@@ -545,7 +808,9 @@ impl PyConvergenceStatus {
|
||||
match &self.inner {
|
||||
entropyk_solver::ConvergenceStatus::Converged => "Converged".to_string(),
|
||||
entropyk_solver::ConvergenceStatus::TimedOutWithBestState => "TimedOut".to_string(),
|
||||
entropyk_solver::ConvergenceStatus::ControlSaturation => "ControlSaturation".to_string(),
|
||||
entropyk_solver::ConvergenceStatus::ControlSaturation => {
|
||||
"ControlSaturation".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -557,7 +822,10 @@ impl PyConvergenceStatus {
|
||||
entropyk_solver::ConvergenceStatus::TimedOutWithBestState
|
||||
),
|
||||
"ControlSaturation" => {
|
||||
matches!(self.inner, entropyk_solver::ConvergenceStatus::ControlSaturation)
|
||||
matches!(
|
||||
self.inner,
|
||||
entropyk_solver::ConvergenceStatus::ControlSaturation
|
||||
)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
@@ -649,3 +917,131 @@ impl PyConvergedState {
|
||||
Ok(numpy::PyArray1::from_vec(py, self.state.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SolverStrategy
|
||||
// =============================================================================
|
||||
|
||||
#[pyclass(name = "SolverStrategy", module = "entropyk")]
|
||||
#[derive(Clone)]
|
||||
pub struct PySolverStrategy {
|
||||
pub(crate) inner: entropyk_solver::SolverStrategy,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PySolverStrategy {
|
||||
#[staticmethod]
|
||||
#[pyo3(signature = (
|
||||
max_iterations=100,
|
||||
tolerance=1e-6,
|
||||
line_search=false,
|
||||
timeout_ms=None,
|
||||
initial_state=None,
|
||||
use_numerical_jacobian=false,
|
||||
jacobian_freezing=None,
|
||||
convergence_criteria=None,
|
||||
timeout_config=None,
|
||||
previous_state=None,
|
||||
line_search_armijo_c=1e-4,
|
||||
line_search_max_backtracks=20,
|
||||
divergence_threshold=1e10
|
||||
))]
|
||||
fn newton(
|
||||
max_iterations: usize,
|
||||
tolerance: f64,
|
||||
line_search: bool,
|
||||
timeout_ms: Option<u64>,
|
||||
initial_state: Option<Vec<f64>>,
|
||||
use_numerical_jacobian: bool,
|
||||
jacobian_freezing: Option<PyJacobianFreezingConfig>,
|
||||
convergence_criteria: Option<PyConvergenceCriteria>,
|
||||
timeout_config: Option<PyTimeoutConfig>,
|
||||
previous_state: Option<Vec<f64>>,
|
||||
line_search_armijo_c: f64,
|
||||
line_search_max_backtracks: usize,
|
||||
divergence_threshold: f64,
|
||||
) -> PyResult<Self> {
|
||||
let py_config = PyNewtonConfig::new(
|
||||
max_iterations, tolerance, line_search, timeout_ms, initial_state,
|
||||
use_numerical_jacobian, jacobian_freezing, convergence_criteria,
|
||||
timeout_config, previous_state, line_search_armijo_c,
|
||||
line_search_max_backtracks, divergence_threshold,
|
||||
)?;
|
||||
let config = entropyk_solver::NewtonConfig {
|
||||
max_iterations: py_config.max_iterations,
|
||||
tolerance: py_config.tolerance,
|
||||
line_search: py_config.line_search,
|
||||
timeout: py_config.timeout_ms.map(Duration::from_millis),
|
||||
use_numerical_jacobian: py_config.use_numerical_jacobian,
|
||||
line_search_armijo_c: py_config.line_search_armijo_c,
|
||||
line_search_max_backtracks: py_config.line_search_max_backtracks,
|
||||
divergence_threshold: py_config.divergence_threshold,
|
||||
timeout_config: py_config.timeout_config.inner.clone(),
|
||||
previous_state: py_config.previous_state.clone(),
|
||||
previous_residual: None,
|
||||
initial_state: py_config.initial_state.clone(),
|
||||
convergence_criteria: py_config.convergence_criteria.as_ref().map(|cc| cc.inner.clone()),
|
||||
jacobian_freezing: py_config.jacobian_freezing.as_ref().map(|jf| jf.inner.clone()),
|
||||
};
|
||||
Ok(PySolverStrategy {
|
||||
inner: entropyk_solver::SolverStrategy::NewtonRaphson(config),
|
||||
})
|
||||
}
|
||||
|
||||
#[staticmethod]
|
||||
#[pyo3(signature = (
|
||||
max_iterations=500,
|
||||
tolerance=1e-4,
|
||||
relaxation=0.5,
|
||||
initial_state=None,
|
||||
timeout_ms=None,
|
||||
convergence_criteria=None
|
||||
))]
|
||||
fn picard(
|
||||
max_iterations: usize,
|
||||
tolerance: f64,
|
||||
relaxation: f64,
|
||||
initial_state: Option<Vec<f64>>,
|
||||
timeout_ms: Option<u64>,
|
||||
convergence_criteria: Option<PyConvergenceCriteria>,
|
||||
) -> PyResult<Self> {
|
||||
let py_config = PyPicardConfig::new(
|
||||
max_iterations, tolerance, relaxation, initial_state, timeout_ms, convergence_criteria
|
||||
)?;
|
||||
let config = entropyk_solver::PicardConfig {
|
||||
max_iterations: py_config.max_iterations,
|
||||
tolerance: py_config.tolerance,
|
||||
relaxation_factor: py_config.relaxation,
|
||||
timeout: py_config.timeout_ms.map(Duration::from_millis),
|
||||
initial_state: py_config.initial_state.clone(),
|
||||
convergence_criteria: py_config.convergence_criteria.as_ref().map(|cc| cc.inner.clone()),
|
||||
..Default::default()
|
||||
};
|
||||
Ok(PySolverStrategy {
|
||||
inner: entropyk_solver::SolverStrategy::SequentialSubstitution(config),
|
||||
})
|
||||
}
|
||||
|
||||
#[staticmethod]
|
||||
fn default() -> Self {
|
||||
PySolverStrategy {
|
||||
inner: entropyk_solver::SolverStrategy::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn solve(&mut self, system: &mut PySystem) -> PyResult<PyConvergedState> {
|
||||
use entropyk_solver::Solver;
|
||||
|
||||
let solve_result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
|
||||
self.inner.solve(&mut system.inner)
|
||||
}));
|
||||
|
||||
match solve_result {
|
||||
Ok(Ok(converged)) => Ok(PyConvergedState::from_rust(converged)),
|
||||
Ok(Err(e)) => Err(solver_error_to_pyerr(e)),
|
||||
Err(_) => Err(PyRuntimeError::new_err(
|
||||
"Internal error: solver panicked. This is a bug — please report it.",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,12 @@ impl PyPressure {
|
||||
/// Create a Pressure. Specify exactly one of: ``pa``, ``bar``, ``kpa``, ``psi``.
|
||||
#[new]
|
||||
#[pyo3(signature = (pa=None, bar=None, kpa=None, psi=None))]
|
||||
fn new(pa: Option<f64>, bar: Option<f64>, kpa: Option<f64>, psi: Option<f64>) -> PyResult<Self> {
|
||||
fn new(
|
||||
pa: Option<f64>,
|
||||
bar: Option<f64>,
|
||||
kpa: Option<f64>,
|
||||
psi: Option<f64>,
|
||||
) -> PyResult<Self> {
|
||||
let value = match (pa, bar, kpa, psi) {
|
||||
(Some(v), None, None, None) => v,
|
||||
(None, Some(v), None, None) => v * 100_000.0,
|
||||
|
||||
3
bindings/python/test_eq_count.py
Normal file
3
bindings/python/test_eq_count.py
Normal file
@@ -0,0 +1,3 @@
|
||||
total = sum(c.n_equations() for c in system.graph.node_weights())
|
||||
# Wait, system in python doesn't expose node_weights.
|
||||
# I'll just run complete_thermodynamic_system.py again
|
||||
Binary file not shown.
Binary file not shown.
@@ -103,7 +103,7 @@ class TestExpansionValve:
|
||||
def test_default(self):
|
||||
v = entropyk.ExpansionValve()
|
||||
assert v.fluid_name == "R134a"
|
||||
assert v.opening_value is None
|
||||
assert v.opening_value == pytest.approx(0.5)
|
||||
|
||||
def test_with_opening(self):
|
||||
v = entropyk.ExpansionValve(opening=0.5)
|
||||
|
||||
@@ -112,6 +112,100 @@ class TestSolverConfigs:
|
||||
config = entropyk.FallbackConfig(newton=newton, picard=picard)
|
||||
assert "50" in repr(config)
|
||||
|
||||
def test_newton_advanced_params(self):
|
||||
config = entropyk.NewtonConfig(
|
||||
initial_state=[1.0, 2.0, 3.0],
|
||||
use_numerical_jacobian=True,
|
||||
line_search_armijo_c=1e-3,
|
||||
line_search_max_backtracks=10,
|
||||
divergence_threshold=1e5
|
||||
)
|
||||
assert config is not None
|
||||
|
||||
def test_picard_advanced_params(self):
|
||||
config = entropyk.PicardConfig(
|
||||
initial_state=[1.0, 2.0],
|
||||
timeout_ms=1000,
|
||||
)
|
||||
assert config is not None
|
||||
|
||||
def test_convergence_criteria(self):
|
||||
cc = entropyk.ConvergenceCriteria(
|
||||
pressure_tolerance_pa=2.0,
|
||||
mass_balance_tolerance_kgs=1e-8,
|
||||
energy_balance_tolerance_w=1e-2
|
||||
)
|
||||
assert "dP=2.0" in repr(cc)
|
||||
assert "dM=1.0" in repr(cc)
|
||||
assert "dE=1.0" in repr(cc)
|
||||
assert cc.pressure_tolerance_pa == 2.0
|
||||
|
||||
config = entropyk.NewtonConfig(convergence_criteria=cc)
|
||||
assert config is not None
|
||||
|
||||
def test_jacobian_freezing(self):
|
||||
jf = entropyk.JacobianFreezingConfig(max_frozen_iters=5, threshold=0.5)
|
||||
assert jf.max_frozen_iters == 5
|
||||
assert jf.threshold == 0.5
|
||||
config = entropyk.NewtonConfig(jacobian_freezing=jf)
|
||||
assert config is not None
|
||||
|
||||
def test_timeout_config(self):
|
||||
tc = entropyk.TimeoutConfig(return_best_state_on_timeout=False, zoh_fallback=True)
|
||||
assert not tc.return_best_state_on_timeout
|
||||
assert tc.zoh_fallback
|
||||
config = entropyk.NewtonConfig(timeout_config=tc)
|
||||
assert config is not None
|
||||
|
||||
def test_solver_strategy(self):
|
||||
newton_strat = entropyk.SolverStrategy.newton(tolerance=1e-5)
|
||||
picard_strat = entropyk.SolverStrategy.picard(relaxation=0.6)
|
||||
default_strat = entropyk.SolverStrategy.default()
|
||||
|
||||
assert newton_strat is not None
|
||||
assert picard_strat is not None
|
||||
assert default_strat is not None
|
||||
|
||||
class TestSolverExecution:
|
||||
"""Tests that the bindings actually call the Rust solver engine."""
|
||||
|
||||
@pytest.fixture
|
||||
def simple_system(self):
|
||||
system = entropyk.System()
|
||||
# Use simple components to avoid complex physics crashes
|
||||
i0 = system.add_component(entropyk.Pipe(length=10.0, diameter=0.1, fluid="Water"))
|
||||
i1 = system.add_component(entropyk.Pipe(length=10.0, diameter=0.1, fluid="Water"))
|
||||
system.add_edge(i0, i1)
|
||||
system.add_edge(i1, i0)
|
||||
system.finalize()
|
||||
return system
|
||||
|
||||
def test_newton_solve(self, simple_system):
|
||||
config = entropyk.NewtonConfig(max_iterations=2, timeout_ms=10)
|
||||
try:
|
||||
result = config.solve(simple_system)
|
||||
assert result is not None
|
||||
except entropyk.SolverError:
|
||||
# We don't care if it fails to converge, only that it crossed the boundary
|
||||
pass
|
||||
|
||||
def test_picard_solve(self, simple_system):
|
||||
config = entropyk.PicardConfig(max_iterations=2, timeout_ms=10)
|
||||
try:
|
||||
result = config.solve(simple_system)
|
||||
assert result is not None
|
||||
except entropyk.SolverError:
|
||||
pass
|
||||
|
||||
def test_strategy_solve(self, simple_system):
|
||||
strategy = entropyk.SolverStrategy.newton(max_iterations=2, timeout_ms=10)
|
||||
try:
|
||||
result = strategy.solve(simple_system)
|
||||
assert result is not None
|
||||
except entropyk.SolverError:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class TestConvergedState:
|
||||
"""Tests for ConvergedState and ConvergenceStatus types."""
|
||||
|
||||
220
bindings/python/uv.lock
generated
220
bindings/python/uv.lock
generated
@@ -2,7 +2,12 @@ version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.9"
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.11'",
|
||||
"python_full_version >= '3.14' and sys_platform == 'win32'",
|
||||
"python_full_version >= '3.14' and sys_platform == 'emscripten'",
|
||||
"python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
|
||||
"python_full_version == '3.10.*'",
|
||||
"python_full_version < '3.10'",
|
||||
]
|
||||
@@ -190,6 +195,8 @@ dependencies = [
|
||||
{ name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
|
||||
{ name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
|
||||
{ name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
||||
{ name = "pandas", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
@@ -197,6 +204,7 @@ requires-dist = [
|
||||
{ name = "ipykernel", specifier = ">=6.31.0" },
|
||||
{ name = "maturin", specifier = ">=1.12.4" },
|
||||
{ name = "numpy", specifier = ">=2.0.2" },
|
||||
{ name = "pandas", specifier = ">=2.3.3" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -264,7 +272,12 @@ name = "ipykernel"
|
||||
version = "7.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.11'",
|
||||
"python_full_version >= '3.14' and sys_platform == 'win32'",
|
||||
"python_full_version >= '3.14' and sys_platform == 'emscripten'",
|
||||
"python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
|
||||
"python_full_version == '3.10.*'",
|
||||
]
|
||||
dependencies = [
|
||||
@@ -343,7 +356,12 @@ name = "ipython"
|
||||
version = "9.10.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.11'",
|
||||
"python_full_version >= '3.14' and sys_platform == 'win32'",
|
||||
"python_full_version >= '3.14' and sys_platform == 'emscripten'",
|
||||
"python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" },
|
||||
@@ -412,7 +430,12 @@ name = "jupyter-client"
|
||||
version = "8.8.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.11'",
|
||||
"python_full_version >= '3.14' and sys_platform == 'win32'",
|
||||
"python_full_version >= '3.14' and sys_platform == 'emscripten'",
|
||||
"python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
|
||||
"python_full_version == '3.10.*'",
|
||||
]
|
||||
dependencies = [
|
||||
@@ -449,7 +472,12 @@ name = "jupyter-core"
|
||||
version = "5.9.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.11'",
|
||||
"python_full_version >= '3.14' and sys_platform == 'win32'",
|
||||
"python_full_version >= '3.14' and sys_platform == 'emscripten'",
|
||||
"python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
|
||||
"python_full_version == '3.10.*'",
|
||||
]
|
||||
dependencies = [
|
||||
@@ -631,7 +659,12 @@ name = "numpy"
|
||||
version = "2.4.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.11'",
|
||||
"python_full_version >= '3.14' and sys_platform == 'win32'",
|
||||
"python_full_version >= '3.14' and sys_platform == 'emscripten'",
|
||||
"python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z" }
|
||||
wheels = [
|
||||
@@ -717,6 +750,147 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pandas"
|
||||
version = "2.3.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version == '3.10.*'",
|
||||
"python_full_version < '3.10'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
|
||||
{ name = "python-dateutil", marker = "python_full_version < '3.11'" },
|
||||
{ name = "pytz", marker = "python_full_version < '3.11'" },
|
||||
{ name = "tzdata", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/f7/f425a00df4fcc22b292c6895c6831c0c8ae1d9fac1e024d16f98a9ce8749/pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c", size = 11555763, upload-time = "2025-09-29T23:16:53.287Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/4f/66d99628ff8ce7857aca52fed8f0066ce209f96be2fede6cef9f84e8d04f/pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a", size = 10801217, upload-time = "2025-09-29T23:17:04.522Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/03/3fc4a529a7710f890a239cc496fc6d50ad4a0995657dccc1d64695adb9f4/pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1", size = 12148791, upload-time = "2025-09-29T23:17:18.444Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/a8/4dac1f8f8235e5d25b9955d02ff6f29396191d4e665d71122c3722ca83c5/pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838", size = 12769373, upload-time = "2025-09-29T23:17:35.846Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/91/82cc5169b6b25440a7fc0ef3a694582418d875c8e3ebf796a6d6470aa578/pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250", size = 13200444, upload-time = "2025-09-29T23:17:49.341Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/ae/89b3283800ab58f7af2952704078555fa60c807fff764395bb57ea0b0dbd/pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4", size = 13858459, upload-time = "2025-09-29T23:18:03.722Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/72/530900610650f54a35a19476eca5104f38555afccda1aa11a92ee14cb21d/pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826", size = 11346086, upload-time = "2025-09-29T23:18:18.505Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", size = 11578790, upload-time = "2025-09-29T23:18:30.065Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", size = 10833831, upload-time = "2025-09-29T23:38:56.071Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", size = 12199267, upload-time = "2025-09-29T23:18:41.627Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", size = 12789281, upload-time = "2025-09-29T23:18:56.834Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", size = 13240453, upload-time = "2025-09-29T23:19:09.247Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", size = 13890361, upload-time = "2025-09-29T23:19:25.342Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", size = 11348702, upload-time = "2025-09-29T23:19:38.296Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/b4/52eeb530a99e2a4c55ffcd352772b599ed4473a0f892d127f4147cf0f88e/pandas-2.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c503ba5216814e295f40711470446bc3fd00f0faea8a086cbc688808e26f92a2", size = 11567720, upload-time = "2025-09-29T23:33:06.209Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/4a/2d8b67632a021bced649ba940455ed441ca854e57d6e7658a6024587b083/pandas-2.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a637c5cdfa04b6d6e2ecedcb81fc52ffb0fd78ce2ebccc9ea964df9f658de8c8", size = 10810302, upload-time = "2025-09-29T23:33:35.846Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/e6/d2465010ee0569a245c975dc6967b801887068bc893e908239b1f4b6c1ac/pandas-2.3.3-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:854d00d556406bffe66a4c0802f334c9ad5a96b4f1f868adf036a21b11ef13ff", size = 12154874, upload-time = "2025-09-29T23:33:49.939Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/18/aae8c0aa69a386a3255940e9317f793808ea79d0a525a97a903366bb2569/pandas-2.3.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf1f8a81d04ca90e32a0aceb819d34dbd378a98bf923b6398b9a3ec0bf44de29", size = 12790141, upload-time = "2025-09-29T23:34:05.655Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/26/617f98de789de00c2a444fbe6301bb19e66556ac78cff933d2c98f62f2b4/pandas-2.3.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:23ebd657a4d38268c7dfbdf089fbc31ea709d82e4923c5ffd4fbd5747133ce73", size = 13208697, upload-time = "2025-09-29T23:34:21.835Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/fb/25709afa4552042bd0e15717c75e9b4a2294c3dc4f7e6ea50f03c5136600/pandas-2.3.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5554c929ccc317d41a5e3d1234f3be588248e61f08a74dd17c9eabb535777dc9", size = 13879233, upload-time = "2025-09-29T23:34:35.079Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/af/7be05277859a7bc399da8ba68b88c96b27b48740b6cf49688899c6eb4176/pandas-2.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:d3e28b3e83862ccf4d85ff19cf8c20b2ae7e503881711ff2d534dc8f761131aa", size = 11359119, upload-time = "2025-09-29T23:34:46.339Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pandas"
|
||||
version = "3.0.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.14' and sys_platform == 'win32'",
|
||||
"python_full_version >= '3.14' and sys_platform == 'emscripten'",
|
||||
"python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
|
||||
{ name = "python-dateutil", marker = "python_full_version >= '3.11'" },
|
||||
{ name = "tzdata", marker = "(python_full_version >= '3.11' and sys_platform == 'emscripten') or (python_full_version >= '3.11' and sys_platform == 'win32')" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2e/0c/b28ed414f080ee0ad153f848586d61d1878f91689950f037f976ce15f6c8/pandas-3.0.1.tar.gz", hash = "sha256:4186a699674af418f655dbd420ed87f50d56b4cd6603784279d9eef6627823c8", size = 4641901, upload-time = "2026-02-17T22:20:16.434Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/07/c7087e003ceee9b9a82539b40414ec557aa795b584a1a346e89180853d79/pandas-3.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de09668c1bf3b925c07e5762291602f0d789eca1b3a781f99c1c78f6cac0e7ea", size = 10323380, upload-time = "2026-02-17T22:18:16.133Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/27/90683c7122febeefe84a56f2cde86a9f05f68d53885cebcc473298dfc33e/pandas-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:24ba315ba3d6e5806063ac6eb717504e499ce30bd8c236d8693a5fd3f084c796", size = 9923455, upload-time = "2026-02-17T22:18:19.13Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/f1/ed17d927f9950643bc7631aa4c99ff0cc83a37864470bc419345b656a41f/pandas-3.0.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:406ce835c55bac912f2a0dcfaf27c06d73c6b04a5dde45f1fd3169ce31337389", size = 10753464, upload-time = "2026-02-17T22:18:21.134Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/7c/870c7e7daec2a6c7ff2ac9e33b23317230d4e4e954b35112759ea4a924a7/pandas-3.0.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:830994d7e1f31dd7e790045235605ab61cff6c94defc774547e8b7fdfbff3dc7", size = 11255234, upload-time = "2026-02-17T22:18:24.175Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/39/3653fe59af68606282b989c23d1a543ceba6e8099cbcc5f1d506a7bae2aa/pandas-3.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a64ce8b0f2de1d2efd2ae40b0abe7f8ae6b29fbfb3812098ed5a6f8e235ad9bf", size = 11767299, upload-time = "2026-02-17T22:18:26.824Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/31/1daf3c0c94a849c7a8dab8a69697b36d313b229918002ba3e409265c7888/pandas-3.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9832c2c69da24b602c32e0c7b1b508a03949c18ba08d4d9f1c1033426685b447", size = 12333292, upload-time = "2026-02-17T22:18:28.996Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/67/af63f83cd6ca603a00fe8530c10a60f0879265b8be00b5930e8e78c5b30b/pandas-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:84f0904a69e7365f79a0c77d3cdfccbfb05bf87847e3a51a41e1426b0edb9c79", size = 9892176, upload-time = "2026-02-17T22:18:31.79Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/ab/9c776b14ac4b7b4140788eca18468ea39894bc7340a408f1d1e379856a6b/pandas-3.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:4a68773d5a778afb31d12e34f7dd4612ab90de8c6fb1d8ffe5d4a03b955082a1", size = 9151328, upload-time = "2026-02-17T22:18:35.721Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/51/b467209c08dae2c624873d7491ea47d2b47336e5403309d433ea79c38571/pandas-3.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:476f84f8c20c9f5bc47252b66b4bb25e1a9fc2fa98cead96744d8116cb85771d", size = 10344357, upload-time = "2026-02-17T22:18:38.262Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/f1/e2567ffc8951ab371db2e40b2fe068e36b81d8cf3260f06ae508700e5504/pandas-3.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0ab749dfba921edf641d4036c4c21c0b3ea70fea478165cb98a998fb2a261955", size = 9884543, upload-time = "2026-02-17T22:18:41.476Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/39/327802e0b6d693182403c144edacbc27eb82907b57062f23ef5a4c4a5ea7/pandas-3.0.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8e36891080b87823aff3640c78649b91b8ff6eea3c0d70aeabd72ea43ab069b", size = 10396030, upload-time = "2026-02-17T22:18:43.822Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/fe/89d77e424365280b79d99b3e1e7d606f5165af2f2ecfaf0c6d24c799d607/pandas-3.0.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:532527a701281b9dd371e2f582ed9094f4c12dd9ffb82c0c54ee28d8ac9520c4", size = 10876435, upload-time = "2026-02-17T22:18:45.954Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/a6/2a75320849dd154a793f69c951db759aedb8d1dd3939eeacda9bdcfa1629/pandas-3.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:356e5c055ed9b0da1580d465657bc7d00635af4fd47f30afb23025352ba764d1", size = 11405133, upload-time = "2026-02-17T22:18:48.533Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/53/1d68fafb2e02d7881df66aa53be4cd748d25cbe311f3b3c85c93ea5d30ca/pandas-3.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9d810036895f9ad6345b8f2a338dd6998a74e8483847403582cab67745bff821", size = 11932065, upload-time = "2026-02-17T22:18:50.837Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/08/67cc404b3a966b6df27b38370ddd96b3b023030b572283d035181854aac5/pandas-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:536232a5fe26dd989bd633e7a0c450705fdc86a207fec7254a55e9a22950fe43", size = 9741627, upload-time = "2026-02-17T22:18:53.905Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/4f/caf9952948fb00d23795f09b893d11f1cacb384e666854d87249530f7cbe/pandas-3.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f463ebfd8de7f326d38037c7363c6dacb857c5881ab8961fb387804d6daf2f7", size = 9052483, upload-time = "2026-02-17T22:18:57.31Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/48/aad6ec4f8d007534c091e9a7172b3ec1b1ee6d99a9cbb936b5eab6c6cf58/pandas-3.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5272627187b5d9c20e55d27caf5f2cd23e286aba25cadf73c8590e432e2b7262", size = 10317509, upload-time = "2026-02-17T22:18:59.498Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/14/5990826f779f79148ae9d3a2c39593dc04d61d5d90541e71b5749f35af95/pandas-3.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:661e0f665932af88c7877f31da0dc743fe9c8f2524bdffe23d24fdcb67ef9d56", size = 9860561, upload-time = "2026-02-17T22:19:02.265Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/80/f01ff54664b6d70fed71475543d108a9b7c888e923ad210795bef04ffb7d/pandas-3.0.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75e6e292ff898679e47a2199172593d9f6107fd2dd3617c22c2946e97d5df46e", size = 10365506, upload-time = "2026-02-17T22:19:05.017Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/85/ab6d04733a7d6ff32bfc8382bf1b07078228f5d6ebec5266b91bfc5c4ff7/pandas-3.0.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ff8cf1d2896e34343197685f432450ec99a85ba8d90cce2030c5eee2ef98791", size = 10873196, upload-time = "2026-02-17T22:19:07.204Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/a9/9301c83d0b47c23ac5deab91c6b39fd98d5b5db4d93b25df8d381451828f/pandas-3.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eca8b4510f6763f3d37359c2105df03a7a221a508f30e396a51d0713d462e68a", size = 11370859, upload-time = "2026-02-17T22:19:09.436Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/fe/0c1fc5bd2d29c7db2ab372330063ad555fb83e08422829c785f5ec2176ca/pandas-3.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:06aff2ad6f0b94a17822cf8b83bbb563b090ed82ff4fe7712db2ce57cd50d9b8", size = 11924584, upload-time = "2026-02-17T22:19:11.562Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/7d/216a1588b65a7aa5f4535570418a599d943c85afb1d95b0876fc00aa1468/pandas-3.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:9fea306c783e28884c29057a1d9baa11a349bbf99538ec1da44c8476563d1b25", size = 9742769, upload-time = "2026-02-17T22:19:13.926Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/cb/810a22a6af9a4e97c8ab1c946b47f3489c5bca5adc483ce0ffc84c9cc768/pandas-3.0.1-cp313-cp313-win_arm64.whl", hash = "sha256:a8d37a43c52917427e897cb2e429f67a449327394396a81034a4449b99afda59", size = 9043855, upload-time = "2026-02-17T22:19:16.09Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/fa/423c89086cca1f039cf1253c3ff5b90f157b5b3757314aa635f6bf3e30aa/pandas-3.0.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d54855f04f8246ed7b6fc96b05d4871591143c46c0b6f4af874764ed0d2d6f06", size = 10752673, upload-time = "2026-02-17T22:19:18.304Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/23/b5a08ec1f40020397f0faba72f1e2c11f7596a6169c7b3e800abff0e433f/pandas-3.0.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e1b677accee34a09e0dc2ce5624e4a58a1870ffe56fc021e9caf7f23cd7668f", size = 10404967, upload-time = "2026-02-17T22:19:20.726Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/81/94841f1bb4afdc2b52a99daa895ac2c61600bb72e26525ecc9543d453ebc/pandas-3.0.1-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a9cabbdcd03f1b6cd254d6dda8ae09b0252524be1592594c00b7895916cb1324", size = 10320575, upload-time = "2026-02-17T22:19:24.919Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/8b/2ae37d66a5342a83adadfd0cb0b4bf9c3c7925424dd5f40d15d6cfaa35ee/pandas-3.0.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ae2ab1f166668b41e770650101e7090824fd34d17915dd9cd479f5c5e0065e9", size = 10710921, upload-time = "2026-02-17T22:19:27.181Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/61/772b2e2757855e232b7ccf7cb8079a5711becb3a97f291c953def15a833f/pandas-3.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6bf0603c2e30e2cafac32807b06435f28741135cb8697eae8b28c7d492fc7d76", size = 11334191, upload-time = "2026-02-17T22:19:29.411Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/08/b16c6df3ef555d8495d1d265a7963b65be166785d28f06a350913a4fac78/pandas-3.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c426422973973cae1f4a23e51d4ae85974f44871b24844e4f7de752dd877098", size = 11782256, upload-time = "2026-02-17T22:19:32.34Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/80/178af0594890dee17e239fca96d3d8670ba0f5ff59b7d0439850924a9c09/pandas-3.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b03f91ae8c10a85c1613102c7bef5229b5379f343030a3ccefeca8a33414cf35", size = 10485047, upload-time = "2026-02-17T22:19:34.605Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/8b/4bb774a998b97e6c2fd62a9e6cfdaae133b636fd1c468f92afb4ae9a447a/pandas-3.0.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:99d0f92ed92d3083d140bf6b97774f9f13863924cf3f52a70711f4e7588f9d0a", size = 10322465, upload-time = "2026-02-17T22:19:36.803Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/3a/5b39b51c64159f470f1ca3b1c2a87da290657ca022f7cd11442606f607d1/pandas-3.0.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3b66857e983208654294bb6477b8a63dee26b37bdd0eb34d010556e91261784f", size = 9910632, upload-time = "2026-02-17T22:19:39.001Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/f7/b449ffb3f68c11da12fc06fbf6d2fa3a41c41e17d0284d23a79e1c13a7e4/pandas-3.0.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56cf59638bf24dc9bdf2154c81e248b3289f9a09a6d04e63608c159022352749", size = 10440535, upload-time = "2026-02-17T22:19:41.157Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/77/6ea82043db22cb0f2bbfe7198da3544000ddaadb12d26be36e19b03a2dc5/pandas-3.0.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1a9f55e0f46951874b863d1f3906dcb57df2d9be5c5847ba4dfb55b2c815249", size = 10893940, upload-time = "2026-02-17T22:19:43.493Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/30/f1b502a72468c89412c1b882a08f6eed8a4ee9dc033f35f65d0663df6081/pandas-3.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1849f0bba9c8a2fb0f691d492b834cc8dadf617e29015c66e989448d58d011ee", size = 11442711, upload-time = "2026-02-17T22:19:46.074Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/f0/ebb6ddd8fc049e98cabac5c2924d14d1dda26a20adb70d41ea2e428d3ec4/pandas-3.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c3d288439e11b5325b02ae6e9cc83e6805a62c40c5a6220bea9beb899c073b1c", size = 11963918, upload-time = "2026-02-17T22:19:48.838Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/f8/8ce132104074f977f907442790eaae24e27bce3b3b454e82faa3237ff098/pandas-3.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:93325b0fe372d192965f4cca88d97667f49557398bbf94abdda3bf1b591dbe66", size = 9862099, upload-time = "2026-02-17T22:19:51.081Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/b7/6af9aac41ef2456b768ef0ae60acf8abcebb450a52043d030a65b4b7c9bd/pandas-3.0.1-cp314-cp314-win_arm64.whl", hash = "sha256:97ca08674e3287c7148f4858b01136f8bdfe7202ad25ad04fec602dd1d29d132", size = 9185333, upload-time = "2026-02-17T22:19:53.266Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/fc/848bb6710bc6061cb0c5badd65b92ff75c81302e0e31e496d00029fe4953/pandas-3.0.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:58eeb1b2e0fb322befcf2bbc9ba0af41e616abadb3d3414a6bc7167f6cbfce32", size = 10772664, upload-time = "2026-02-17T22:19:55.806Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/69/5c/866a9bbd0f79263b4b0db6ec1a341be13a1473323f05c122388e0f15b21d/pandas-3.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cd9af1276b5ca9e298bd79a26bda32fa9cc87ed095b2a9a60978d2ca058eaf87", size = 10421286, upload-time = "2026-02-17T22:19:58.091Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/a4/2058fb84fb1cfbfb2d4a6d485e1940bb4ad5716e539d779852494479c580/pandas-3.0.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f87a04984d6b63788327cd9f79dda62b7f9043909d2440ceccf709249ca988", size = 10342050, upload-time = "2026-02-17T22:20:01.376Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/1b/674e89996cc4be74db3c4eb09240c4bb549865c9c3f5d9b086ff8fcfbf00/pandas-3.0.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85fe4c4df62e1e20f9db6ebfb88c844b092c22cd5324bdcf94bfa2fc1b391221", size = 10740055, upload-time = "2026-02-17T22:20:04.328Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/f8/e954b750764298c22fa4614376531fe63c521ef517e7059a51f062b87dca/pandas-3.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:331ca75a2f8672c365ae25c0b29e46f5ac0c6551fdace8eec4cd65e4fac271ff", size = 11357632, upload-time = "2026-02-17T22:20:06.647Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/02/c6e04b694ffd68568297abd03588b6d30295265176a5c01b7459d3bc35a3/pandas-3.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:15860b1fdb1973fffade772fdb931ccf9b2f400a3f5665aef94a00445d7d8dd5", size = 11810974, upload-time = "2026-02-17T22:20:08.946Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/41/d7dfb63d2407f12055215070c42fc6ac41b66e90a2946cdc5e759058398b/pandas-3.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:44f1364411d5670efa692b146c748f4ed013df91ee91e9bec5677fb1fd58b937", size = 10884622, upload-time = "2026-02-17T22:20:11.711Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/b0/34937815889fa982613775e4b97fddd13250f11012d769949c5465af2150/pandas-3.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:108dd1790337a494aa80e38def654ca3f0968cf4f362c85f44c15e471667102d", size = 9452085, upload-time = "2026-02-17T22:20:14.331Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parso"
|
||||
version = "0.8.6"
|
||||
@@ -731,7 +905,7 @@ name = "pexpect"
|
||||
version = "4.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "ptyprocess" },
|
||||
{ name = "ptyprocess", marker = "(python_full_version < '3.11' and sys_platform == 'emscripten') or (python_full_version < '3.11' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" }
|
||||
wheels = [
|
||||
@@ -755,7 +929,12 @@ name = "platformdirs"
|
||||
version = "4.9.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.11'",
|
||||
"python_full_version >= '3.14' and sys_platform == 'win32'",
|
||||
"python_full_version >= '3.14' and sys_platform == 'emscripten'",
|
||||
"python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
|
||||
"python_full_version == '3.10.*'",
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1b/04/fea538adf7dbbd6d186f551d595961e564a3b6715bdf276b477460858672/platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291", size = 28394, upload-time = "2026-02-16T03:56:10.574Z" }
|
||||
@@ -838,7 +1017,12 @@ name = "pycparser"
|
||||
version = "3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.11'",
|
||||
"python_full_version >= '3.14' and sys_platform == 'win32'",
|
||||
"python_full_version >= '3.14' and sys_platform == 'emscripten'",
|
||||
"python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'win32'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform == 'emscripten'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
|
||||
"python_full_version == '3.10.*'",
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" }
|
||||
@@ -867,6 +1051,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytz"
|
||||
version = "2025.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pywin32"
|
||||
version = "311"
|
||||
@@ -1094,6 +1287,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tzdata"
|
||||
version = "2025.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wcwidth"
|
||||
version = "0.6.0"
|
||||
|
||||
Reference in New Issue
Block a user