combaero-gui 0.3.1.dev2__tar.gz → 0.4.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/PKG-INFO +2 -2
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/backend/graph_builder.py +235 -40
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/backend/main.py +9 -3
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/backend/runner.py +0 -18
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/backend/schemas.py +63 -14
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/combaero_gui.egg-info/PKG-INFO +2 -2
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/combaero_gui.egg-info/SOURCES.txt +11 -5
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/combaero_gui.egg-info/requires.txt +1 -1
- combaero_gui-0.4.2/combaero_gui.egg-info/scm_file_list.json +76 -0
- combaero_gui-0.4.2/combaero_gui.egg-info/scm_version.json +8 -0
- combaero_gui-0.4.2/frontend/dist/assets/index-ImDBQgh3.js +20 -0
- combaero_gui-0.4.2/frontend/dist/assets/index-hDCh9ZPl.css +2 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/dist/index.html +2 -2
- combaero_gui-0.4.2/frontend/package.json +44 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/pnpm-lock.yaml +557 -549
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/api.ts +17 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/components/AreaInput.tsx +3 -1
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/components/CompositionEditor.tsx +2 -0
- combaero_gui-0.4.2/frontend/src/components/ConvergenceChart.tsx +287 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/components/FlowEdge.tsx +3 -34
- combaero_gui-0.4.2/frontend/src/components/Inspector.tsx +3155 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/components/LengthInput.tsx +3 -1
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/components/NetworkCanvas.tsx +11 -6
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/components/NumericInput.tsx +6 -1
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/components/Sidebar.tsx +32 -13
- combaero_gui-0.4.2/frontend/src/components/SolverStatusBadge.tsx +134 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/components/ThermalEdge.tsx +3 -33
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/components/UnitInput.tsx +4 -1
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/components/flowTypes.ts +4 -2
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/components/nodes/AreaChangeNode.tsx +25 -4
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/components/nodes/ChannelNode.tsx +25 -4
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/components/nodes/CombustorNode.tsx +25 -4
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/components/nodes/DiscreteLossNode.tsx +25 -4
- combaero_gui-0.4.2/frontend/src/components/nodes/MPCETeeNode.tsx +152 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/components/nodes/MassBoundaryNode.tsx +13 -2
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/components/nodes/MomentumChamberNode.tsx +25 -4
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/components/nodes/OrificeNode.tsx +25 -4
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/components/nodes/PlenumNode.tsx +25 -4
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/components/nodes/PressureBoundaryNode.tsx +13 -2
- combaero_gui-0.4.2/frontend/src/components/nodes/WallNode.tsx +59 -0
- combaero_gui-0.4.2/frontend/src/hooks/useCopyPaste.ts +83 -0
- combaero_gui-0.4.2/frontend/src/store/useStore.test.ts +158 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/store/useStore.ts +50 -11
- combaero_gui-0.4.2/frontend/src/utils/nodeUtils.ts +77 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/utils/quantities.ts +6 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/utils/validation.ts +21 -13
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/pyproject.toml +1 -1
- combaero_gui-0.3.1.dev2/frontend/dist/assets/index-BACdja6I.js +0 -19
- combaero_gui-0.3.1.dev2/frontend/dist/assets/index-mUR2pMTx.css +0 -2
- combaero_gui-0.3.1.dev2/frontend/package.json +0 -44
- combaero_gui-0.3.1.dev2/frontend/src/components/Inspector.tsx +0 -2095
- combaero_gui-0.3.1.dev2/frontend/src/components/SolverStatusBadge.tsx +0 -71
- combaero_gui-0.3.1.dev2/frontend/src/components/nodes/TeeJunctionNode.tsx +0 -136
- combaero_gui-0.3.1.dev2/frontend/src/utils/nodeUtils.ts +0 -11
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/MANIFEST.in +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/README.md +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/__init__.py +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/backend/__init__.py +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/backend/units.py +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/combaero_gui.egg-info/dependency_links.txt +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/combaero_gui.egg-info/entry_points.txt +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/combaero_gui.egg-info/top_level.txt +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/.gitignore +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/README.md +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/dist/favicon.svg +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/dist/icons.svg +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/eslint.config.js +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/index.html +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/postcss.config.js +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/public/favicon.svg +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/public/icons.svg +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/App.css +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/App.tsx +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/assets/hero.png +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/assets/react.svg +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/assets/vite.svg +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/components/DiagnosticsPanel.tsx +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/components/NodeDiagRows.tsx +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/components/SurfaceEnhancementInspector.tsx +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/components/WallLayersEditor.tsx +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/components/nodes/LosslessNode.tsx +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/components/nodes/ProbeNode.tsx +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/components/nodes/VortexNode.tsx +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/index.css +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/main.tsx +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/node-25-regression.test.ts +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/src/utils/diagnostics.ts +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/tailwind.config.js +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/tsconfig.app.json +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/tsconfig.json +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/tsconfig.node.json +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/vite.config.ts +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/frontend/vitest.config.ts +0 -0
- {combaero_gui-0.3.1.dev2 → combaero_gui-0.4.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: combaero-gui
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: CombAero Network GUI — React/FastAPI desktop application
|
|
5
5
|
Author-email: thiemom <2641939+thiemom@users.noreply.github.com>
|
|
6
6
|
License: MIT
|
|
@@ -21,7 +21,7 @@ Requires-Dist: fastapi
|
|
|
21
21
|
Requires-Dist: uvicorn
|
|
22
22
|
Requires-Dist: pydantic
|
|
23
23
|
Requires-Dist: pandas
|
|
24
|
-
Requires-Dist: combaero~=0.
|
|
24
|
+
Requires-Dist: combaero~=0.4
|
|
25
25
|
Provides-Extra: gui
|
|
26
26
|
|
|
27
27
|
# CombAero Network Designer GUI
|
|
@@ -22,11 +22,12 @@ from combaero.network import (
|
|
|
22
22
|
PressureLossElement,
|
|
23
23
|
RibbedModel,
|
|
24
24
|
SmoothModel,
|
|
25
|
-
TeeJunctionElement,
|
|
26
25
|
ThermalWall,
|
|
27
26
|
VortexElement,
|
|
28
27
|
WallLayer,
|
|
28
|
+
WallNode,
|
|
29
29
|
)
|
|
30
|
+
from combaero.network.mpce_v2_element import ConstantKTeeElement, MPCEv2Element
|
|
30
31
|
|
|
31
32
|
from .schemas import (
|
|
32
33
|
AreaChangeData,
|
|
@@ -42,6 +43,7 @@ from .schemas import (
|
|
|
42
43
|
LinearThetaHeadLossData,
|
|
43
44
|
MassBoundaryData,
|
|
44
45
|
MomentumChamberData,
|
|
46
|
+
MPCETeeData,
|
|
45
47
|
NetworkGraphSchema,
|
|
46
48
|
OrificeData,
|
|
47
49
|
PinFinModelData,
|
|
@@ -49,9 +51,9 @@ from .schemas import (
|
|
|
49
51
|
PressureBoundaryData,
|
|
50
52
|
RibbedModelData,
|
|
51
53
|
SmoothModelData,
|
|
52
|
-
TeeJunctionData,
|
|
53
54
|
ThermalWallData,
|
|
54
55
|
VortexData,
|
|
56
|
+
WallData,
|
|
55
57
|
)
|
|
56
58
|
|
|
57
59
|
|
|
@@ -194,9 +196,14 @@ def _guess_from_prior_result(node_data: dict, prefix: str) -> dict:
|
|
|
194
196
|
solves on a difficult network benefit from accumulated Newton progress.
|
|
195
197
|
Works for both pressure-node results (state.P/T/Pt/Tt) and element
|
|
196
198
|
results (m_dot stored at the top level).
|
|
199
|
+
|
|
200
|
+
Only CONVERGED prior results are used: the best iterate of a failed
|
|
201
|
+
solve is stored too, and seeding from it parks Newton at the stall
|
|
202
|
+
point -- one failed solve (e.g. a timeout) would otherwise poison
|
|
203
|
+
every subsequent run of a network that converges fine from cold.
|
|
197
204
|
"""
|
|
198
205
|
prior = node_data.get("result")
|
|
199
|
-
if not prior:
|
|
206
|
+
if not prior or not prior.get("success"):
|
|
200
207
|
return {}
|
|
201
208
|
short: dict[str, float] = {}
|
|
202
209
|
state = prior.get("state")
|
|
@@ -235,6 +242,93 @@ def _build_discrete_loss_correlation(
|
|
|
235
242
|
return ConstantFractionLoss(xi=xi)
|
|
236
243
|
|
|
237
244
|
|
|
245
|
+
def _channel_D_at_tee_port(
|
|
246
|
+
schema_edges,
|
|
247
|
+
schema_nodes,
|
|
248
|
+
tee_id: str,
|
|
249
|
+
port: str,
|
|
250
|
+
) -> float | None:
|
|
251
|
+
"""Return the inner diameter [m] of a channel directly connected to a tee port.
|
|
252
|
+
|
|
253
|
+
The user-facing schema connects channels straight to tee handles (the
|
|
254
|
+
port-MCN is auto-inserted at network-build time, not at schema level).
|
|
255
|
+
Used by ``mpce_tee`` dispatch to inherit ``F_C`` / ``F_branch``
|
|
256
|
+
from the connected channel geometry when the
|
|
257
|
+
user has not explicitly set them. Returns None when no channel is
|
|
258
|
+
directly connected (e.g., plenum, boundary, multi-hop).
|
|
259
|
+
"""
|
|
260
|
+
src_handle = f"port-{port}-source"
|
|
261
|
+
tgt_handle = f"port-{port}-target"
|
|
262
|
+
for edge in schema_edges:
|
|
263
|
+
if edge.data and edge.data.get("type") == "thermal":
|
|
264
|
+
continue
|
|
265
|
+
neighbour_id: str | None = None
|
|
266
|
+
if edge.source == tee_id and edge.sourceHandle == src_handle:
|
|
267
|
+
neighbour_id = edge.target
|
|
268
|
+
elif edge.target == tee_id and edge.targetHandle == tgt_handle:
|
|
269
|
+
neighbour_id = edge.source
|
|
270
|
+
if neighbour_id is None:
|
|
271
|
+
continue
|
|
272
|
+
neighbour = next((n for n in schema_nodes if n.id == neighbour_id), None)
|
|
273
|
+
if neighbour is None or neighbour.type != "channel":
|
|
274
|
+
continue
|
|
275
|
+
d = neighbour.data.get("D") if isinstance(neighbour.data, dict) else None
|
|
276
|
+
if d is None:
|
|
277
|
+
continue
|
|
278
|
+
try:
|
|
279
|
+
d_val = float(d)
|
|
280
|
+
except (TypeError, ValueError):
|
|
281
|
+
continue
|
|
282
|
+
if d_val > 0.0:
|
|
283
|
+
return d_val
|
|
284
|
+
return None
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _resolve_tee_areas(
|
|
288
|
+
schema_edges,
|
|
289
|
+
schema_nodes,
|
|
290
|
+
tee_id: str,
|
|
291
|
+
F_C_explicit: float | None,
|
|
292
|
+
F_branch_explicit: float | None,
|
|
293
|
+
psi_fallback: float,
|
|
294
|
+
) -> tuple[float, float, float]:
|
|
295
|
+
"""Resolve (F_C, F_branch, psi) for a 3-port tee, applying inheritance.
|
|
296
|
+
|
|
297
|
+
Precedence:
|
|
298
|
+
1. Explicit user-set value (non-null) wins.
|
|
299
|
+
2. Channel D at the connected port (auto-inherited).
|
|
300
|
+
3. Fallback: F_C=0.01 m^2, F_branch derived from psi_fallback.
|
|
301
|
+
|
|
302
|
+
Returns (F_C, F_branch, psi) all resolved to concrete floats.
|
|
303
|
+
"""
|
|
304
|
+
F_C = F_C_explicit
|
|
305
|
+
if F_C is None:
|
|
306
|
+
# Try common port first, then straight (both are F_C in Bassett/Idelchik
|
|
307
|
+
# geometry where F_s = F_c by definition).
|
|
308
|
+
for port in ("common", "straight"):
|
|
309
|
+
d = _channel_D_at_tee_port(schema_edges, schema_nodes, tee_id, port)
|
|
310
|
+
if d is not None:
|
|
311
|
+
F_C = _math.pi / 4.0 * d * d
|
|
312
|
+
break
|
|
313
|
+
if F_C is None:
|
|
314
|
+
F_C = 0.01 # ultimate default for a fully disconnected tee
|
|
315
|
+
|
|
316
|
+
F_branch = F_branch_explicit
|
|
317
|
+
if F_branch is None:
|
|
318
|
+
d = _channel_D_at_tee_port(schema_edges, schema_nodes, tee_id, "branch")
|
|
319
|
+
if d is not None:
|
|
320
|
+
F_branch = _math.pi / 4.0 * d * d
|
|
321
|
+
|
|
322
|
+
if F_branch is not None and F_branch > 0.0:
|
|
323
|
+
psi = F_C / F_branch
|
|
324
|
+
else:
|
|
325
|
+
# No branch channel; use stored psi to derive F_branch
|
|
326
|
+
psi = max(float(psi_fallback), 1e-12)
|
|
327
|
+
F_branch = F_C / psi
|
|
328
|
+
|
|
329
|
+
return F_C, F_branch, psi
|
|
330
|
+
|
|
331
|
+
|
|
238
332
|
def _port_from_handle(handle: str | None) -> str | None:
|
|
239
333
|
"""Extract port name from a tee handle string.
|
|
240
334
|
|
|
@@ -256,6 +350,7 @@ def _find_tee_port(
|
|
|
256
350
|
port: str,
|
|
257
351
|
nodes_map: dict,
|
|
258
352
|
tee_port_node_map: dict | None = None,
|
|
353
|
+
wall_edge_remap: dict | None = None,
|
|
259
354
|
) -> str:
|
|
260
355
|
"""Return the physical-node ID connected to a named tee port.
|
|
261
356
|
|
|
@@ -276,6 +371,8 @@ def _find_tee_port(
|
|
|
276
371
|
else:
|
|
277
372
|
continue
|
|
278
373
|
if nid in nodes_map:
|
|
374
|
+
if wall_edge_remap and edge.id in wall_edge_remap:
|
|
375
|
+
return wall_edge_remap[edge.id]
|
|
279
376
|
return nid
|
|
280
377
|
if tee_port_node_map and (tee_id, port) in tee_port_node_map:
|
|
281
378
|
return tee_port_node_map[(tee_id, port)]
|
|
@@ -335,7 +432,7 @@ def build_network_from_schema(schema: NetworkGraphSchema) -> FlowNetwork:
|
|
|
335
432
|
area=data.area,
|
|
336
433
|
Dh=data.Dh,
|
|
337
434
|
surface=ConvectiveSurface(
|
|
338
|
-
area=data.area,
|
|
435
|
+
area=data.area or 0.0,
|
|
339
436
|
model=map_surface_model(data.surface),
|
|
340
437
|
Nu_multiplier=data.Nu_multiplier
|
|
341
438
|
* (schema.solver_settings.Nu_multiplier or 1.0),
|
|
@@ -352,8 +449,9 @@ def build_network_from_schema(schema: NetworkGraphSchema) -> FlowNetwork:
|
|
|
352
449
|
node = MomentumChamberNode(
|
|
353
450
|
node_id,
|
|
354
451
|
area=data.area,
|
|
452
|
+
Dh=data.Dh,
|
|
355
453
|
surface=ConvectiveSurface(
|
|
356
|
-
area=data.area,
|
|
454
|
+
area=data.area or 0.0,
|
|
357
455
|
model=map_surface_model(data.surface),
|
|
358
456
|
Nu_multiplier=data.Nu_multiplier
|
|
359
457
|
* (schema.solver_settings.Nu_multiplier or 1.0),
|
|
@@ -367,12 +465,18 @@ def build_network_from_schema(schema: NetworkGraphSchema) -> FlowNetwork:
|
|
|
367
465
|
nodes_map[node_id] = node
|
|
368
466
|
elif node_type == "probe":
|
|
369
467
|
pass # Display-only diagnostic nodes -- not part of the solver graph
|
|
468
|
+
elif node_type == "wall":
|
|
469
|
+
data = WallData(**node_data)
|
|
470
|
+
node = WallNode(node_id)
|
|
471
|
+
node.initial_guess = _expand_initial_guess(data.initial_guess, node_id)
|
|
472
|
+
net.add_node(node)
|
|
473
|
+
nodes_map[node_id] = node
|
|
370
474
|
else:
|
|
371
475
|
# This is an element node (Channel, Orifice, etc.)
|
|
372
476
|
element_nodes.append(node_schema)
|
|
373
477
|
|
|
374
478
|
element_ids = {node.id for node in element_nodes}
|
|
375
|
-
tee_ids = {node.id for node in element_nodes if node.type == "
|
|
479
|
+
tee_ids = {node.id for node in element_nodes if node.type == "mpce_tee"}
|
|
376
480
|
|
|
377
481
|
# 1b. Create implicit junction nodes for element -> element visual links.
|
|
378
482
|
# For direct element<->element connections we auto-insert a MomentumChamberNode.
|
|
@@ -398,58 +502,138 @@ def build_network_from_schema(schema: NetworkGraphSchema) -> FlowNetwork:
|
|
|
398
502
|
edge_junction_map[(edge.source, edge.target)] = junction_id
|
|
399
503
|
|
|
400
504
|
elif src_tee and tgt_elem:
|
|
401
|
-
# Tee port → element (e.g. tee.common-source → channel): auto-insert
|
|
505
|
+
# Tee port → element (e.g. tee.common-source → channel): auto-insert MomentumChamberNode
|
|
506
|
+
# so that Pt != P_static and velocity is correctly accounted for.
|
|
402
507
|
port = _port_from_handle(edge.sourceHandle)
|
|
403
508
|
if port and (edge.source, port) not in tee_port_node_map:
|
|
404
509
|
jid = f"__tee_jct__{edge.source}_{port}"
|
|
405
510
|
if jid not in nodes_map:
|
|
406
|
-
jn =
|
|
511
|
+
jn = MomentumChamberNode(jid)
|
|
407
512
|
net.add_node(jn)
|
|
408
513
|
nodes_map[jid] = jn
|
|
409
514
|
tee_port_node_map[(edge.source, port)] = jid
|
|
410
515
|
edge_junction_map[(edge.source, edge.target)] = jid
|
|
411
516
|
|
|
412
517
|
elif src_elem and tgt_tee:
|
|
413
|
-
# Element → tee port (e.g. channel → tee.straight-target): auto-insert
|
|
518
|
+
# Element → tee port (e.g. channel → tee.straight-target): auto-insert MomentumChamberNode
|
|
414
519
|
port = _port_from_handle(edge.targetHandle)
|
|
415
520
|
if port and (edge.target, port) not in tee_port_node_map:
|
|
416
521
|
jid = f"__tee_jct__{edge.target}_{port}"
|
|
417
522
|
if jid not in nodes_map:
|
|
418
|
-
jn =
|
|
523
|
+
jn = MomentumChamberNode(jid)
|
|
419
524
|
net.add_node(jn)
|
|
420
525
|
nodes_map[jid] = jn
|
|
421
526
|
tee_port_node_map[(edge.target, port)] = jid
|
|
422
527
|
edge_junction_map[(edge.source, edge.target)] = jid
|
|
423
528
|
|
|
529
|
+
# Pre-scan: for each edge whose target is a WallNode, the 2nd+ connection
|
|
530
|
+
# gets its own clone so the solver enforces m_dot=0 per branch individually
|
|
531
|
+
# (not just sum(m_dot)=0, which allows non-zero flows to cancel).
|
|
532
|
+
# This covers both regular elements and tee junction port connections.
|
|
533
|
+
_wall_edge_remap: dict[str, str] = {} # edge_id -> effective target node_id
|
|
534
|
+
_wall_target_count: dict[str, int] = {}
|
|
535
|
+
for _edge in schema.edges:
|
|
536
|
+
if _edge.data and _edge.data.get("type") == "thermal":
|
|
537
|
+
continue
|
|
538
|
+
_tgt = _edge.target
|
|
539
|
+
if isinstance(nodes_map.get(_tgt), WallNode):
|
|
540
|
+
_wall_target_count[_tgt] = _wall_target_count.get(_tgt, 0) + 1
|
|
541
|
+
if _wall_target_count[_tgt] > 1:
|
|
542
|
+
_n = _wall_target_count[_tgt]
|
|
543
|
+
_clone_id = f"{_tgt}_w{_n}"
|
|
544
|
+
_clone = WallNode(_clone_id)
|
|
545
|
+
net.add_node(_clone)
|
|
546
|
+
nodes_map[_clone_id] = _clone
|
|
547
|
+
_wall_edge_remap[_edge.id] = _clone_id
|
|
548
|
+
|
|
424
549
|
# 2. Second Pass: Create Elements
|
|
425
550
|
for elem_node_schema in element_nodes:
|
|
426
551
|
elem_id = elem_node_schema.id
|
|
427
552
|
elem_data = elem_node_schema.data
|
|
428
553
|
elem_type = elem_node_schema.type
|
|
429
554
|
|
|
430
|
-
# 3-port element
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
psi=_d.psi,
|
|
447
|
-
tee_type=_d.tee_type,
|
|
555
|
+
# 3-port element (Mynard-based). The flow_direction field constrains
|
|
556
|
+
# which side is upstream.
|
|
557
|
+
if elem_type == "mpce_tee":
|
|
558
|
+
_md = MPCETeeData(**elem_data)
|
|
559
|
+
# Resolve areas with inheritance precedence (see _resolve_tee_areas).
|
|
560
|
+
_F_C, _A_branch, _psi = _resolve_tee_areas(
|
|
561
|
+
schema.edges,
|
|
562
|
+
schema.nodes,
|
|
563
|
+
elem_id,
|
|
564
|
+
F_C_explicit=_md.F_C,
|
|
565
|
+
F_branch_explicit=_md.F_branch,
|
|
566
|
+
psi_fallback=_md.psi,
|
|
567
|
+
)
|
|
568
|
+
|
|
569
|
+
_common = _find_tee_port(
|
|
570
|
+
schema.edges, elem_id, "common", nodes_map, tee_port_node_map, _wall_edge_remap
|
|
448
571
|
)
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
572
|
+
_straight = _find_tee_port(
|
|
573
|
+
schema.edges, elem_id, "straight", nodes_map, tee_port_node_map, _wall_edge_remap
|
|
574
|
+
)
|
|
575
|
+
_branch = _find_tee_port(
|
|
576
|
+
schema.edges, elem_id, "branch", nodes_map, tee_port_node_map, _wall_edge_remap
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
# Branch (separating): single inlet at common, two outlets.
|
|
580
|
+
# Merge (joining): two inlets at straight+branch, single outlet at common.
|
|
581
|
+
if _md.flow_direction == "branch":
|
|
582
|
+
_inlets = [_common]
|
|
583
|
+
_outlets = [_straight, _branch]
|
|
584
|
+
_inlet_angles = [0.0]
|
|
585
|
+
_outlet_angles = [0.0, _md.theta_deg]
|
|
586
|
+
_port_areas = [_F_C, _F_C, _A_branch]
|
|
587
|
+
else: # "merge"
|
|
588
|
+
_inlets = [_straight, _branch]
|
|
589
|
+
_outlets = [_common]
|
|
590
|
+
_inlet_angles = [0.0, _md.theta_deg]
|
|
591
|
+
_outlet_angles = [0.0]
|
|
592
|
+
_port_areas = [_F_C, _A_branch, _F_C]
|
|
593
|
+
|
|
594
|
+
# strict=False: strict mode raises whenever a Newton ITERATE
|
|
595
|
+
# (not the converged answer) explores a wrong-sign port
|
|
596
|
+
# m_dot, killing GUI solves that would otherwise converge
|
|
597
|
+
# ("Unexpected error during residual evaluation"). Soft mode
|
|
598
|
+
# lets the solver explore; the post-solve
|
|
599
|
+
# verify_solution_consistent guard in NetworkSolver rejects
|
|
600
|
+
# any wrong-direction artifact root at convergence.
|
|
601
|
+
if _md.junction_model == "constant_k":
|
|
602
|
+
# Simplest-model tier: fixed handbook K per non-common
|
|
603
|
+
# port, referenced to the common-leg dynamic head. Port
|
|
604
|
+
# order is inlets first: branch = [common, straight,
|
|
605
|
+
# branch]; merge = [straight, branch, common].
|
|
606
|
+
if _md.flow_direction == "branch":
|
|
607
|
+
_k_ports = {1: _md.K_straight, 2: _md.K_branch}
|
|
608
|
+
else:
|
|
609
|
+
_k_ports = {0: _md.K_straight, 1: _md.K_branch}
|
|
610
|
+
_mpce = ConstantKTeeElement(
|
|
611
|
+
id=elem_id,
|
|
612
|
+
inlet_nodes=_inlets,
|
|
613
|
+
outlet_nodes=_outlets,
|
|
614
|
+
inlet_angles_deg=_inlet_angles,
|
|
615
|
+
outlet_angles_deg=_outlet_angles,
|
|
616
|
+
port_areas=_port_areas,
|
|
617
|
+
flow_direction=_md.flow_direction,
|
|
618
|
+
strict=False,
|
|
619
|
+
K_ports=_k_ports,
|
|
620
|
+
)
|
|
621
|
+
else:
|
|
622
|
+
_mpce = MPCEv2Element(
|
|
623
|
+
id=elem_id,
|
|
624
|
+
inlet_nodes=_inlets,
|
|
625
|
+
outlet_nodes=_outlets,
|
|
626
|
+
inlet_angles_deg=_inlet_angles,
|
|
627
|
+
outlet_angles_deg=_outlet_angles,
|
|
628
|
+
port_areas=_port_areas,
|
|
629
|
+
flow_direction=_md.flow_direction,
|
|
630
|
+
strict=False,
|
|
631
|
+
joining_etransfer_alpha=_md.joining_etransfer_alpha,
|
|
632
|
+
)
|
|
633
|
+
_mpce.initial_guess = _expand_initial_guess(_md.initial_guess, elem_id)
|
|
634
|
+
if not _mpce.initial_guess:
|
|
635
|
+
_mpce.initial_guess = _guess_from_prior_result(elem_data, elem_id)
|
|
636
|
+
net.add_element(_mpce)
|
|
453
637
|
continue
|
|
454
638
|
|
|
455
639
|
source_id = None
|
|
@@ -473,7 +657,7 @@ def build_network_from_schema(schema: NetworkGraphSchema) -> FlowNetwork:
|
|
|
473
657
|
if outgoing_count > 1:
|
|
474
658
|
raise ValueError(f"Element '{elem_id}' has multiple downstream links.")
|
|
475
659
|
if edge.target in nodes_map:
|
|
476
|
-
target_id = edge.target
|
|
660
|
+
target_id = _wall_edge_remap.get(edge.id, edge.target)
|
|
477
661
|
elif edge.target in element_ids:
|
|
478
662
|
target_id = edge_junction_map.get((elem_id, edge.target))
|
|
479
663
|
|
|
@@ -482,13 +666,15 @@ def build_network_from_schema(schema: NetworkGraphSchema) -> FlowNetwork:
|
|
|
482
666
|
|
|
483
667
|
if elem_type == "channel":
|
|
484
668
|
data = ChannelData(**elem_data)
|
|
485
|
-
conv_area
|
|
669
|
+
# conv_area is deferred to 0 when D=None; resolve_topology updates it
|
|
670
|
+
conv_area = (3.1415926535 * data.D * data.L) if data.D is not None else 0.0
|
|
486
671
|
elem = ChannelElement(
|
|
487
672
|
elem_id,
|
|
488
673
|
from_node=source_id,
|
|
489
674
|
to_node=target_id,
|
|
490
675
|
length=data.L,
|
|
491
676
|
diameter=data.D,
|
|
677
|
+
Dh=data.Dh,
|
|
492
678
|
roughness=data.roughness,
|
|
493
679
|
friction_model=data.friction_model,
|
|
494
680
|
surface=ConvectiveSurface(
|
|
@@ -548,17 +734,26 @@ def build_network_from_schema(schema: NetworkGraphSchema) -> FlowNetwork:
|
|
|
548
734
|
net.add_element(elem)
|
|
549
735
|
elif elem_type == "discrete_loss":
|
|
550
736
|
data = DiscreteLossData(**elem_data)
|
|
551
|
-
#
|
|
552
|
-
area
|
|
737
|
+
# area=None signals "inherit from upstream element in resolve_topology".
|
|
738
|
+
# Use a quick node-area lookup only when data.area is explicitly set or
|
|
739
|
+
# the upstream node exposes a non-zero area (combustor / momentum chamber).
|
|
740
|
+
area: float | None = data.area
|
|
553
741
|
if area is None and source_id in nodes_map:
|
|
554
|
-
|
|
555
|
-
|
|
742
|
+
node_area = getattr(nodes_map[source_id], "area", None)
|
|
743
|
+
if node_area and node_area > 0:
|
|
744
|
+
area = node_area
|
|
556
745
|
correlation = _build_discrete_loss_correlation(
|
|
557
|
-
data.correlation_type,
|
|
746
|
+
data.correlation_type,
|
|
747
|
+
data.xi,
|
|
748
|
+
data.k,
|
|
749
|
+
data.xi0,
|
|
750
|
+
data.zeta,
|
|
751
|
+
data.zeta0,
|
|
752
|
+
area if area and area > 0 else 0.1,
|
|
558
753
|
)
|
|
559
754
|
theta_source = data.theta_source if data.theta_source not in (None, "none") else None
|
|
560
755
|
surface = ConvectiveSurface(
|
|
561
|
-
area=area,
|
|
756
|
+
area=area if area and area > 0 else 0.0,
|
|
562
757
|
model=map_surface_model(data.surface),
|
|
563
758
|
Nu_multiplier=data.Nu_multiplier,
|
|
564
759
|
f_multiplier=data.f_multiplier,
|
|
@@ -128,7 +128,8 @@ def _solve_sync(schema: NetworkGraphSchema):
|
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
node_results, element_results, edge_results = _build_result_objects(result, net)
|
|
131
|
-
|
|
131
|
+
diag = getattr(solver, "_diagnostic_data", {})
|
|
132
|
+
return result, node_results, element_results, edge_results, net, diag
|
|
132
133
|
|
|
133
134
|
|
|
134
135
|
@app.post("/solve", response_model=SolveResponse)
|
|
@@ -140,7 +141,7 @@ async def solve(schema: NetworkGraphSchema):
|
|
|
140
141
|
hard_timeout = soft_timeout + 10.0
|
|
141
142
|
try:
|
|
142
143
|
# Offload CPU-bound C++ solver to a worker thread
|
|
143
|
-
result, node_results, element_results, edge_results, _ = await asyncio.wait_for(
|
|
144
|
+
result, node_results, element_results, edge_results, _, diag = await asyncio.wait_for(
|
|
144
145
|
asyncio.to_thread(_solve_sync, schema),
|
|
145
146
|
timeout=hard_timeout,
|
|
146
147
|
)
|
|
@@ -152,12 +153,17 @@ async def solve(schema: NetworkGraphSchema):
|
|
|
152
153
|
node_results=node_results,
|
|
153
154
|
element_results=element_results,
|
|
154
155
|
edge_results=edge_results,
|
|
156
|
+
convergence_history=diag.get("convergence_history", []),
|
|
157
|
+
worst_residuals=diag.get("worst_residuals", []),
|
|
158
|
+
solver_settings_used=diag.get("solver_settings_used", {}),
|
|
159
|
+
lm_started_at_eval=diag.get("lm_started_at_eval"),
|
|
155
160
|
)
|
|
156
161
|
except TimeoutError:
|
|
157
162
|
raise HTTPException(
|
|
158
163
|
status_code=504,
|
|
159
164
|
detail=f"Solver exceeded the hard timeout of {hard_timeout:.0f}s. "
|
|
160
|
-
"Try
|
|
165
|
+
"Try raising the solver timeout, simplifying the network, or a "
|
|
166
|
+
"different init strategy.",
|
|
161
167
|
) from None
|
|
162
168
|
except ValueError as e:
|
|
163
169
|
raise HTTPException(status_code=400, detail=str(e)) from e
|
|
@@ -230,8 +230,6 @@ class NetworkResult:
|
|
|
230
230
|
with all available diagnostic quantities. Column names carry unit
|
|
231
231
|
annotations matching the GUI export (e.g. ``'P [Pa]'``, ``'T [K]'``).
|
|
232
232
|
"""
|
|
233
|
-
from combaero.network.components import TeeJunctionElement as _TeeJE
|
|
234
|
-
|
|
235
233
|
_BOUNDARY_KINDS = {"mass_boundary", "pressure_boundary"}
|
|
236
234
|
species_labels: list[str] = list(cb.species.names)
|
|
237
235
|
data: list[dict] = []
|
|
@@ -261,22 +259,6 @@ class NetworkResult:
|
|
|
261
259
|
|
|
262
260
|
for elem_id, res in self._element_results.items():
|
|
263
261
|
elem_data = res.model_dump()
|
|
264
|
-
elem_obj = self._net.elements.get(elem_id)
|
|
265
|
-
if isinstance(elem_obj, _TeeJE):
|
|
266
|
-
common_res = self._node_results.get(elem_obj.common_node)
|
|
267
|
-
if common_res:
|
|
268
|
-
common_state = common_res.state.model_dump()
|
|
269
|
-
Y = common_state.pop("Y", [])
|
|
270
|
-
X = common_state.pop("X", None) or []
|
|
271
|
-
for k, v in common_state.items():
|
|
272
|
-
if v is not None:
|
|
273
|
-
elem_data[k] = v
|
|
274
|
-
for i, y_val in enumerate(Y):
|
|
275
|
-
name = species_labels[i] if i < len(species_labels) else str(i)
|
|
276
|
-
elem_data[f"Y[{name}]"] = y_val
|
|
277
|
-
for i, x_val in enumerate(X):
|
|
278
|
-
name = species_labels[i] if i < len(species_labels) else str(i)
|
|
279
|
-
elem_data[f"X[{name}]"] = x_val
|
|
280
262
|
data.append(
|
|
281
263
|
{
|
|
282
264
|
"type": "element",
|
|
@@ -88,6 +88,12 @@ class PressureBoundaryData(BaseModel):
|
|
|
88
88
|
label: str | None = None
|
|
89
89
|
|
|
90
90
|
|
|
91
|
+
class WallData(BaseModel):
|
|
92
|
+
model_config = ConfigDict(extra="ignore")
|
|
93
|
+
label: str | None = None
|
|
94
|
+
initial_guess: dict[str, float] = Field(default_factory=dict)
|
|
95
|
+
|
|
96
|
+
|
|
91
97
|
class ConstantFractionLossData(BaseModel):
|
|
92
98
|
model_config = ConfigDict(extra="ignore")
|
|
93
99
|
type: Literal["constant_fraction"] = "constant_fraction"
|
|
@@ -125,7 +131,7 @@ PressureLossData = (
|
|
|
125
131
|
class CombustorData(BaseModel):
|
|
126
132
|
model_config = ConfigDict(extra="ignore")
|
|
127
133
|
method: Literal["complete", "equilibrium"] = "complete"
|
|
128
|
-
area: float =
|
|
134
|
+
area: float | None = None # None = derive from Dh (pi/4 * Dh^2)
|
|
129
135
|
Dh: float | None = None
|
|
130
136
|
surface: SurfaceModelData = Field(default_factory=SmoothModelData)
|
|
131
137
|
Nu_multiplier: float = 1.0
|
|
@@ -136,8 +142,8 @@ class CombustorData(BaseModel):
|
|
|
136
142
|
|
|
137
143
|
class MomentumChamberData(BaseModel):
|
|
138
144
|
model_config = ConfigDict(extra="ignore")
|
|
139
|
-
area: float =
|
|
140
|
-
Dh: float =
|
|
145
|
+
area: float | None = None # None = derive from Dh (pi/4 * Dh^2)
|
|
146
|
+
Dh: float | None = None # None = inherit from upstream channel (Dh = D)
|
|
141
147
|
surface: SurfaceModelData = Field(default_factory=SmoothModelData)
|
|
142
148
|
Nu_multiplier: float = 1.0
|
|
143
149
|
f_multiplier: float = 1.0
|
|
@@ -151,7 +157,8 @@ class MomentumChamberData(BaseModel):
|
|
|
151
157
|
class ChannelData(BaseModel):
|
|
152
158
|
model_config = ConfigDict(extra="ignore")
|
|
153
159
|
L: float = 1.0
|
|
154
|
-
D: float =
|
|
160
|
+
D: float | None = None # None = inherit from upstream element geometry
|
|
161
|
+
Dh: float | None = None # None = circular (Dh = D); override for non-circular ducts
|
|
155
162
|
roughness: float = 1e-5
|
|
156
163
|
friction_model: Literal["haaland", "serghides", "colebrook", "petukhov"] = "haaland"
|
|
157
164
|
surface: SurfaceModelData = Field(default_factory=SmoothModelData)
|
|
@@ -164,7 +171,7 @@ class ChannelData(BaseModel):
|
|
|
164
171
|
|
|
165
172
|
class OrificeData(BaseModel):
|
|
166
173
|
model_config = ConfigDict(extra="ignore")
|
|
167
|
-
diameter: float =
|
|
174
|
+
diameter: float | None = None # None = inherit from upstream channel
|
|
168
175
|
Cd: float = 0.6
|
|
169
176
|
correlation: Literal[
|
|
170
177
|
"ReaderHarrisGallagher", "Stolz", "Miller", "ThickPlate", "RoundedEntry", "fixed"
|
|
@@ -190,8 +197,8 @@ class OrificeData(BaseModel):
|
|
|
190
197
|
class AreaChangeData(BaseModel):
|
|
191
198
|
model_config = ConfigDict(extra="ignore")
|
|
192
199
|
model_type: Literal["sharp", "conical"] = "sharp"
|
|
193
|
-
F0: float =
|
|
194
|
-
F1: float =
|
|
200
|
+
F0: float | None = None # None = inherit from upstream channel (A = pi/4*D^2)
|
|
201
|
+
F1: float | None = None # None = inherit from downstream channel
|
|
195
202
|
length: float | None = None
|
|
196
203
|
D_h: float = 0.0
|
|
197
204
|
initial_guess: dict[str, float] = Field(default_factory=dict)
|
|
@@ -202,13 +209,35 @@ class LosslessConnectionData(BaseModel):
|
|
|
202
209
|
pass
|
|
203
210
|
|
|
204
211
|
|
|
205
|
-
class
|
|
212
|
+
class MPCETeeData(BaseModel):
|
|
213
|
+
"""Mynard-based multi-port chamber tee element with constrained topology.
|
|
214
|
+
|
|
215
|
+
3-port junction using Mynard's Unified0D pseudosupplier residual. The
|
|
216
|
+
``flow_direction`` field declares whether this junction is a merge
|
|
217
|
+
(joining flow, 2 inlets + 1 outlet) or a branch (separating flow,
|
|
218
|
+
1 inlet + 2 outlets); the runtime element raises if the observed
|
|
219
|
+
flow pattern disagrees.
|
|
220
|
+
"""
|
|
221
|
+
|
|
206
222
|
model_config = ConfigDict(extra="ignore")
|
|
207
223
|
label: str | None = None
|
|
208
|
-
|
|
224
|
+
flow_direction: Literal["merge", "branch"] = "branch"
|
|
225
|
+
# Junction closure model. "mynard" (default) is the full Unified0D
|
|
226
|
+
# pseudosupplier physics; "constant_k" is the simplest-model tier:
|
|
227
|
+
# fixed handbook loss coefficients referenced to the common-leg
|
|
228
|
+
# dynamic head (Idelchik convention), K independent of the split.
|
|
229
|
+
junction_model: Literal["mynard", "constant_k"] = "mynard"
|
|
230
|
+
K_straight: float = 0.4 # constant_k only: straight-arm loss coeff [-]
|
|
231
|
+
K_branch: float = 1.0 # constant_k only: branch-arm loss coeff [-]
|
|
209
232
|
theta_deg: float = 90.0 # branch angle [deg], converted to radians in graph_builder
|
|
210
|
-
F_C: float =
|
|
211
|
-
|
|
233
|
+
F_C: float | None = None # None = inherit from common/straight-arm channel
|
|
234
|
+
F_branch: float | None = None # None = inherit from branch-arm channel
|
|
235
|
+
psi: float = 1.0 # fallback ratio used only when F_branch is None and not inherited
|
|
236
|
+
# Joining-side etransfer correction scale (combaero extension to faithful
|
|
237
|
+
# Mynard). Default 0.2 calibrated against Bassett K11/K12 analytical +
|
|
238
|
+
# Idelchik 1966 tabulated values. Set to 0.0 to disable the correction
|
|
239
|
+
# (faithful Mynard); set to a custom value for re-calibrated workflows.
|
|
240
|
+
joining_etransfer_alpha: float | None = None
|
|
212
241
|
initial_guess: dict[str, float] = Field(default_factory=dict)
|
|
213
242
|
|
|
214
243
|
|
|
@@ -276,9 +305,14 @@ class ThermalWallData(BaseModel):
|
|
|
276
305
|
class SolverSettings(BaseModel):
|
|
277
306
|
model_config = ConfigDict(extra="ignore")
|
|
278
307
|
global_regime: Literal["incompressible", "compressible"] = "incompressible"
|
|
279
|
-
init_strategy: Literal[
|
|
280
|
-
"default"
|
|
281
|
-
|
|
308
|
+
init_strategy: Literal[
|
|
309
|
+
"default",
|
|
310
|
+
"analytical_pt_prop",
|
|
311
|
+
"incompressible_warmstart",
|
|
312
|
+
"outletref_warmstart",
|
|
313
|
+
"homotopy",
|
|
314
|
+
"continuation",
|
|
315
|
+
] = "default"
|
|
282
316
|
method: Literal[
|
|
283
317
|
"hybr",
|
|
284
318
|
"lm",
|
|
@@ -375,6 +409,17 @@ class ElementResult(BaseModel):
|
|
|
375
409
|
success: bool = True
|
|
376
410
|
|
|
377
411
|
|
|
412
|
+
class ConvergencePoint(BaseModel):
|
|
413
|
+
eval: int
|
|
414
|
+
t: float
|
|
415
|
+
norm: float
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
class WorstResidual(BaseModel):
|
|
419
|
+
name: str
|
|
420
|
+
residual: float
|
|
421
|
+
|
|
422
|
+
|
|
378
423
|
class SolveResponse(BaseModel):
|
|
379
424
|
success: bool
|
|
380
425
|
message: str = ""
|
|
@@ -382,3 +427,7 @@ class SolveResponse(BaseModel):
|
|
|
382
427
|
node_results: dict[str, NodeResult] = {}
|
|
383
428
|
element_results: dict[str, ElementResult] = {}
|
|
384
429
|
edge_results: dict[str, dict] = {}
|
|
430
|
+
convergence_history: list[ConvergencePoint] = []
|
|
431
|
+
worst_residuals: list[WorstResidual] = []
|
|
432
|
+
solver_settings_used: dict = {}
|
|
433
|
+
lm_started_at_eval: int | None = None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: combaero-gui
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: CombAero Network GUI — React/FastAPI desktop application
|
|
5
5
|
Author-email: thiemom <2641939+thiemom@users.noreply.github.com>
|
|
6
6
|
License: MIT
|
|
@@ -21,7 +21,7 @@ Requires-Dist: fastapi
|
|
|
21
21
|
Requires-Dist: uvicorn
|
|
22
22
|
Requires-Dist: pydantic
|
|
23
23
|
Requires-Dist: pandas
|
|
24
|
-
Requires-Dist: combaero~=0.
|
|
24
|
+
Requires-Dist: combaero~=0.4
|
|
25
25
|
Provides-Extra: gui
|
|
26
26
|
|
|
27
27
|
# CombAero Network Designer GUI
|