Maintainer triage (June 29, 2026)
- Status: Verified bug against the current
master source in this fork.
- Severity: Critical.
- Area labels: area:simexec, area:flowsheet.
- Tackle batch: P1 - critical blockers and severe unit errors.
The source review confirmed that the cited code path and failure mode are still present. The original audit details are preserved below for reproduction notes and suggested fixes.
Original audit details
Part of the model-correctness audit — see #17.
Severity: critical | Category: logic | Location: PharmaPy/SimExec.py lines 129-139, 149-153
What's wrong
After solving a unit name, the destination of the Connection is taken as self.execution_names[ind + 1] (the next unit in the topological execution order), while the actual successor(s) of name are in neighbors = self.graph[name]. neighbors is only used as a boolean gate (len(neighbors) > 0); its contents are ignored when choosing the destination. So connection = Connection(source_uo=getattr(self, name), destination_uo=getattr(self, uo_next)) with uo_next = execution_names[ind+1] routes the outlet to whatever unit happens to be next in the sorted order, not to the true downstream unit.
Why it's incorrect
Topological order is not unique and does not equal the physical edge list except for a single linear chain. Example: graph A->C, B->C, C->D (Mixer C fed by A and B). A valid topological order from topological_bfs is [A, B, C, D]. Processing A (ind=0): neighbors=[C] so the gate passes, but execution_names[1]=B, so transfer_data() copies A.Outlet into B's Inlet/Phases. A's material/energy is delivered to the wrong unit and B's true feed is overwritten. This violates mass and energy conservation across the flowsheet (a stream is delivered to a unit that is not connected to its source). It only coincides with correct behavior when the flowsheet is a single linear chain where topological order equals the edge sequence.
Suggested regression test
Build a Y-shaped flowsheet {'A':['C'], 'B':['C'], 'C':['D']} where A, B carry distinct compositions. After SolveFlowsheet, assert that C.Inlets (Mixer) received A.Outlet and B.Outlet, and that no Connection has destination == B. Then assert total mass into C equals mass out of A plus mass out of B. The current code will instead create a Connection A->B.
Verification: 1/1 adversarial reviewers confirmed (refuted: 0).
Maintainer triage (June 29, 2026)
mastersource in this fork.The source review confirmed that the cited code path and failure mode are still present. The original audit details are preserved below for reproduction notes and suggested fixes.
Original audit details
Part of the model-correctness audit — see #17.
Severity: critical | Category: logic | Location:
PharmaPy/SimExec.pylines 129-139, 149-153What's wrong
After solving a unit
name, the destination of the Connection is taken asself.execution_names[ind + 1](the next unit in the topological execution order), while the actual successor(s) ofnameare inneighbors = self.graph[name].neighborsis only used as a boolean gate (len(neighbors) > 0); its contents are ignored when choosing the destination. Soconnection = Connection(source_uo=getattr(self, name), destination_uo=getattr(self, uo_next))withuo_next = execution_names[ind+1]routes the outlet to whatever unit happens to be next in the sorted order, not to the true downstream unit.Why it's incorrect
Topological order is not unique and does not equal the physical edge list except for a single linear chain. Example: graph A->C, B->C, C->D (Mixer C fed by A and B). A valid topological order from topological_bfs is [A, B, C, D]. Processing A (ind=0): neighbors=[C] so the gate passes, but execution_names[1]=B, so transfer_data() copies A.Outlet into B's Inlet/Phases. A's material/energy is delivered to the wrong unit and B's true feed is overwritten. This violates mass and energy conservation across the flowsheet (a stream is delivered to a unit that is not connected to its source). It only coincides with correct behavior when the flowsheet is a single linear chain where topological order equals the edge sequence.
Suggested regression test
Build a Y-shaped flowsheet {'A':['C'], 'B':['C'], 'C':['D']} where A, B carry distinct compositions. After SolveFlowsheet, assert that C.Inlets (Mixer) received A.Outlet and B.Outlet, and that no Connection has destination == B. Then assert total mass into C equals mass out of A plus mass out of B. The current code will instead create a Connection A->B.
Verification: 1/1 adversarial reviewers confirmed (refuted: 0).