ThermoSim 3.2.0__tar.gz → 3.3.0__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.
Files changed (29) hide show
  1. thermosim-3.3.0/CHANGELOG.md +436 -0
  2. {thermosim-3.2.0 → thermosim-3.3.0}/LICENSE +1 -1
  3. {thermosim-3.2.0 → thermosim-3.3.0}/MANIFEST.in +1 -1
  4. thermosim-3.3.0/PKG-INFO +185 -0
  5. thermosim-3.3.0/README.md +151 -0
  6. thermosim-3.3.0/ThermoSim/T66.json +69 -0
  7. {thermosim-3.2.0 → thermosim-3.3.0}/ThermoSim/__init__.py +1 -1
  8. {thermosim-3.2.0 → thermosim-3.3.0}/ThermoSim/analysis.py +58 -19
  9. {thermosim-3.2.0 → thermosim-3.3.0}/ThermoSim/heat_exchangers.py +959 -364
  10. {thermosim-3.2.0 → thermosim-3.3.0}/ThermoSim/model.py +355 -118
  11. {thermosim-3.2.0 → thermosim-3.3.0}/ThermoSim/plotting.py +599 -236
  12. {thermosim-3.2.0 → thermosim-3.3.0}/ThermoSim/simple_components.py +95 -57
  13. {thermosim-3.2.0 → thermosim-3.3.0}/ThermoSim/state.py +159 -37
  14. {thermosim-3.2.0 → thermosim-3.3.0}/ThermoSim/turbomachinery.py +56 -12
  15. thermosim-3.3.0/ThermoSim.egg-info/PKG-INFO +185 -0
  16. {thermosim-3.2.0 → thermosim-3.3.0}/ThermoSim.egg-info/SOURCES.txt +1 -2
  17. {thermosim-3.2.0 → thermosim-3.3.0}/ThermoSim.egg-info/requires.txt +1 -0
  18. {thermosim-3.2.0 → thermosim-3.3.0}/pyproject.toml +11 -5
  19. thermosim-3.2.0/CHANGELOG.md +0 -97
  20. thermosim-3.2.0/PKG-INFO +0 -165
  21. thermosim-3.2.0/README.md +0 -127
  22. thermosim-3.2.0/ThermoSim/heat_exchangers_old.py +0 -1293
  23. thermosim-3.2.0/ThermoSim.egg-info/PKG-INFO +0 -165
  24. thermosim-3.2.0/setup.py +0 -60
  25. {thermosim-3.2.0 → thermosim-3.3.0}/ThermoSim/base_component.py +0 -0
  26. {thermosim-3.2.0 → thermosim-3.3.0}/ThermoSim/config.py +0 -0
  27. {thermosim-3.2.0 → thermosim-3.3.0}/ThermoSim.egg-info/dependency_links.txt +0 -0
  28. {thermosim-3.2.0 → thermosim-3.3.0}/ThermoSim.egg-info/top_level.txt +0 -0
  29. {thermosim-3.2.0 → thermosim-3.3.0}/setup.cfg +0 -0
@@ -0,0 +1,436 @@
1
+
2
+ # Changelog
3
+ ## [3.3.0] - 2026-08-29
4
+
5
+ ### Added
6
+ - **`Model.Solve()` uses a staged default solve order instead of one flat
7
+ priority pass.** Previously every iteration attempted pass-through
8
+ components, then junctions, then `SimpleHEX`, then other heat exchangers
9
+ sorted by hot-inlet temperature, all in a single pass — so a harder heat
10
+ exchanger could be attempted in the same round as an easier one still
11
+ waiting on upstream data, and heat exchangers were interleaved with a
12
+ still-settling simple network rather than solved after it stabilised.
13
+
14
+ The default order is now staged: pass-through and junction components
15
+ (Turbine, Pump, Compressor, Pipe, Expansion Valve, Splitter, Separator,
16
+ Mixer) are round-robined together to convergence; then `SimpleHEX`,
17
+ round-robined to convergence; then pinch-type heat exchangers
18
+ (`double_pipe`, `Condenser`, `Evaporator`), solved in ascending order of
19
+ *current* unknown-count — every 1-unknown exchanger fully converged
20
+ before any 2-unknown one is attempted, then 3-unknown — with the
21
+ unknown-count re-evaluated every pass, since resolving one exchanger can
22
+ drop another's tier; then everything else, round-robined to convergence.
23
+ The whole sequence repeats every `Solve()` iteration. `custom_order` is
24
+ unchanged.
25
+
26
+ `Model.Solve()` had no test coverage before this change — every existing
27
+ test and example bypasses it via `Calculate=True` in hand-ordered
28
+ construction. Added `tests/test_solve.py`, including a check that a
29
+ cycle built in a deliberately scrambled order converges to the same
30
+ state as the same cycle built in correct dependency order.
31
+
32
+ Also fixed a latent bug this exposed: `HeatExchanger._identify_unknowns()`
33
+ read `self.Hot_In`/`Hot_Out`/`Cold_In`/`Cold_Out`, which were previously
34
+ only ever set inside `Cal()` — calling it on a HEX before its first
35
+ `Cal()` (which the new tiering logic needs to do) raised `AttributeError`
36
+ instead of correctly reporting "everything unknown". Fixed by
37
+ initialising those attributes to `None` in `HeatExchanger.__init__`.
38
+
39
+ - **`CyclePlotter.plot_Ts_diagram` / `plot_Ph_diagram` / `plot_hs_diagram`
40
+ can now plot several cycles on one diagram**, e.g. a cascade system's
41
+ two loops:
42
+
43
+ ```python
44
+ plotter.plot_Ph_diagram([
45
+ ['8', '9', '1', '2', '3', '4'],
46
+ ['3b', '5', '6', '7'],
47
+ ])
48
+ ```
49
+
50
+ `loop_points` accepts either a flat point-name list (unchanged single-
51
+ cycle behaviour) or a list of such lists. Each loop is drawn in its own
52
+ color with its own legend entry, and gets its own saturation dome —
53
+ auto-detected from its own first point, not a single global fluid — so a
54
+ cascade's mismatched refrigerants each get a correctly labelled dome.
55
+ Loops don't need to be point-disjoint: a cascade condenser's two sides
56
+ are free to share a point name if that's how the model is built. CSV
57
+ export tags every row with a `Loop` column.
58
+
59
+ Added two new optional parameters, `loop_labels` and `loop_colors`
60
+ (one entry per loop, `None` falls back to the auto value), for the case
61
+ where several loops share a fluid — e.g. a dual-pressure ORC's two
62
+ stages — where the auto legend label only differs by number
63
+ (`'Loop 1 (R245fa)'` / `'Loop 2 (R245fa)'`) and doesn't say which loop is
64
+ which:
65
+
66
+ ```python
67
+ plotter.plot_Ph_diagram(
68
+ [['h1', 'h2', 'h3', 'h4'], ['l1', 'l2', 'l3', 'l4']],
69
+ loop_labels=['High-Pressure Loop', 'Low-Pressure Loop'],
70
+ loop_colors=['crimson', 'navy'],
71
+ )
72
+ ```
73
+
74
+ `CyclePlotter` had no test coverage before this change. Added
75
+ `tests/test_plotting.py`, covering single-loop backward compatibility,
76
+ multi-fluid cascade rendering, same-fluid dual-pressure labeling, and
77
+ the `loop_labels`/`loop_colors` overrides.
78
+
79
+ ### Fixed
80
+ - **The discretised pinch no longer depends on `div_N`.** `_build_profile`
81
+ (used by both the solver's pinch constraint and `plot_hex_profile`) marched
82
+ the exchanger in `div_N` equal-enthalpy steps. For a phase-changing stream
83
+ the true pinch sits exactly at a saturation crossing — onset of boiling,
84
+ superheating, condensation or subcooling — and a uniform grid only landed
85
+ on it by chance. The reported minimum approach converged from above as
86
+ `div_N` increased instead of being exact at any resolution (documented as
87
+ a known limitation in 3.2.4, see [Troubleshooting](https://github.com/Nouman090/ThermoSim/wiki/Troubleshooting)).
88
+
89
+ ThermoSim now computes the exact enthalpy of every such crossing on both
90
+ streams (via CoolProp's saturated-liquid/-vapour enthalpy at the stream's
91
+ pressure) and inserts it into the profile in addition to the uniform
92
+ `div_N` grid, so the reported pinch is exact at `div_N=10` and `div_N=5000`
93
+ alike. Verified on a water/water double-pipe exchanger where the hot
94
+ stream condenses across the unit: `min_dT` was resolution-dependent before
95
+ the fix (20.000 K at `div_N=10` drifting toward 19.028 K by `div_N=5000`,
96
+ never settling) and is now exactly 19.024401 K at every `div_N` tested,
97
+ 10 through 5000.
98
+
99
+ This changes solved results for exchangers where a stream crosses its
100
+ saturation dome and the pinch constraint resolves an unknown (mass flow,
101
+ enthalpy). The worked example in [Heat Exchanger](https://github.com/Nouman090/ThermoSim/wiki/Heat-Exchanger)
102
+ moves `mc` from 13.521 to 13.553 kg/s; `Q` is unaffected there because it
103
+ comes directly from the fully-specified hot side, not from the pinch.
104
+ Fluids with no two-phase envelope at the operating pressure (incompressible
105
+ fluids, supercritical pressure) are unaffected.
106
+
107
+ ## [3.2.4] - 2026-08-20
108
+
109
+ ### Added
110
+ - **Full-profile pinch validation.** `HeatExchanger` now checks the minimum
111
+ approach along the whole exchanger after every solve, not only at the two
112
+ ends, and reports a violation with `min_dT` stored on the component.
113
+
114
+ The previous check compared only the terminal temperature differences.
115
+ That is sufficient for a monotonic single-phase exchanger, but when
116
+ either stream changes phase the closest approach sits *inside* the unit —
117
+ typically where a boiling stream reaches saturation — and both ends can
118
+ be comfortably clear while the profiles cross in the middle.
119
+
120
+ The underlying asymmetry was that the pinch constraint only binds where
121
+ it is doing work. With two or three unknowns the solver drives
122
+ `min(dT) - PPT` to zero, so the constraint holds by construction. With
123
+ zero or one unknown the energy balance closes the problem on its own and
124
+ `PPT` never enters the calculation, so the exchanger reported convergence
125
+ whatever the profile did.
126
+
127
+ A combined-cycle HRSG built that way returned 53.87 % thermal efficiency
128
+ with every component solved and a minimum approach of **-12.9 K**: the
129
+ temperature profiles crossed and the design was not realisable. It also
130
+ scored higher than the feasible alternative (51.98 % at +15.9 K), because
131
+ relaxing a constraint that is not enforced always improves the objective.
132
+ In a parameter sweep the infeasible design is therefore selected
133
+ preferentially.
134
+
135
+ - `strict_pinch` argument on `HeatExchanger` (default `False`). A violated
136
+ pinch warns by default, since an infeasible point in a sweep is a
137
+ statement about the design rather than an error in the input. Set `True`
138
+ to raise instead.
139
+ - `min_dT` attribute on `HeatExchanger`, so the pinch can be tested
140
+ programmatically rather than read off a plot.
141
+
142
+ ## [3.2.3] - 2026-08-18
143
+
144
+ ### Added
145
+ - **Gas working fluids in pinch-constrained heat exchangers.** Bracket
146
+ bounds now respect the melting line `Tmelt(p)` obtained from
147
+ `AbstractState.melting_line()`, rather than `PropsSI('TMIN', fluid)`
148
+ alone.
149
+
150
+ `TMIN` is a single number, but the melting line varies with pressure and
151
+ can rise above it — for air, 59.75 K against 59.93 K at 10 bar — at which
152
+ point CoolProp refuses the evaluation. Every two- and three-unknown pinch
153
+ problem therefore failed for `Air`, `Nitrogen`, `CO2` and `Oxygen`, i.e.
154
+ for every gas-turbine, HRSG and recuperator model. Water is the opposite
155
+ case: its melting line falls with pressure, so taking the maximum of the
156
+ two handles both directions. Fluids without a fitted melting line, and
157
+ CO2 below its triple-point pressure, fall back to `TMIN`.
158
+
159
+ - `Condition` column and `condition` argument on `Model.Point_print()`.
160
+ Since `Q` became strictly numeric in 3.2.2, single-phase rows showed a
161
+ bare `NaN`; `Condition` carries the readable label without putting a
162
+ string back into a numeric column. The full label, with its margin from
163
+ saturation, remains in `phase`.
164
+
165
+ - **Real process paths in cycle diagrams.** `plot_Ts_diagram`,
166
+ `plot_Ph_diagram` and `plot_hs_diagram` gained `process_path=True`
167
+ (default) and `path_N=60`. Isobaric segments now show their saturation
168
+ crossings, so boiler and condenser legs render as sub-cooled → saturated
169
+ liquid → saturated vapour → superheated with sharp kinks and a flat
170
+ two-phase section; throttling stays isenthalpic; and expansion or
171
+ compression follows a constant-efficiency condition line.
172
+
173
+ Interpolating `(P, H)` linearly across a turbine makes entropy *fall*
174
+ mid-expansion (6.661 → 6.090 → 7.265 kJ/kg·K), which no adiabatic machine
175
+ does; the condition line gives a monotonic 6.661 → 6.709 → 7.265. The
176
+ efficiency is derived from the two endpoints rather than read from
177
+ `n_isen`, so the path stays correct when the outlet state was supplied
178
+ directly. Segments that cannot be traced fall back to a straight line
179
+ silently; `process_path=False` restores the previous polygon.
180
+
181
+ - Validation suite in `validation/`, covering nine worked examples from
182
+ Moran & Shapiro 5th ed. (SI) and the Chena Hot Springs geothermal ORC.
183
+ - Continuous integration on GitHub Actions across Python 3.9–3.13.
184
+ - `CONTRIBUTING.md` and `CODE_OF_CONDUCT.md`.
185
+
186
+ ### Changed
187
+ - **Effectiveness uses the rigorous enthalpy-based definition.**
188
+ `Q_max = min[mh(h_hi - h_h(T_ci)), mc(h_c(T_hi) - h_ci)]`, of which the
189
+ familiar `Cmin·dT` form is the constant-`cp` simplification. For air
190
+ between 453 K and 1138 K — the span of a typical regenerator — `cp` rises
191
+ about 19 %, and that assumption cost 6.4 kJ/kg on the regenerator cold
192
+ outlet of Moran Example 9.11, moving the cycle efficiency deviation from
193
+ 0.85 % to 0.09 %. The enthalpy form was already used for two-phase
194
+ inlets, so this also removes an internal inconsistency.
195
+
196
+ - **Root-finding brackets floored at the counter-flow limit.** No state in
197
+ a counter-flow exchanger is colder than the cold inlet, so the bracket
198
+ need not reach the fluid's absolute validity floor. With the tighter
199
+ bracket `brentq` converges directly instead of falling back to
200
+ differential evolution: **62× faster** on gas cases (252 s → 4.1 s), with
201
+ identical duties and a slightly more accurate pinch.
202
+
203
+ - The `PPT` end-check tolerance is now relative, `max(1e-2, 1e-3·PPT)`. The
204
+ pinch is located by a root-finder with a relative tolerance, so a fixed
205
+ absolute allowance rejected good solutions on large pinches — a 30 K
206
+ target converging to 29.986 K is 0.05 % out, i.e. numerical noise, yet
207
+ was reported as a physical violation.
208
+
209
+ - `requires-python` raised to `>=3.9`, with per-version classifiers, to
210
+ match what is actually tested.
211
+ - `setup.py` removed; `pyproject.toml` is the single source of truth. The
212
+ two had drifted to different version strings.
213
+
214
+ ### Fixed
215
+ - `ThermodynamicModel.add_point()` now returns the created `Prop`. It
216
+ registered the point but returned `None`, so the documented
217
+ `inlet = Model.add_point(...)` capture pattern raised `AttributeError`.
218
+ - `Pipe` distinguishes an explicitly lossless line from an unspecified one.
219
+ `Pressure_drop` and `Temperature_drop` default to `None` and are tested
220
+ with `is not None`; they previously defaulted to `0` and were tested with
221
+ `!= 0`, so `Pipe(..., Pressure_drop=0)` raised
222
+ `Need at least 2 of (P_in, P_out, Pressure_drop)`.
223
+ - `Turbine`, `Pump` and `Compressor` raise on a mass-flow mismatch between
224
+ their ports, via the shared `_resolve_mass_flowrate` helper. Previously
225
+ the mismatch was silent: work was computed from the inlet and the outlet
226
+ left unchanged.
227
+ - Defaulted mass flows are no longer propagated into the model. The 1 kg/s
228
+ fallback is a guess, and writing it to a shared state point caused a
229
+ later component that resolved the real rate to see a spurious mismatch.
230
+ - A lower enthalpy bound subtracted its margin instead of adding it,
231
+ placing the bound below the valid region.
232
+ ## [3.2.2] - 2026-08-07
233
+
234
+ ### Fixed — packaging
235
+ - `tabulate` added to the declared dependencies. It is imported at module
236
+ scope in `model.py`, so a clean `pip install ThermoSim` followed by
237
+ `import ThermoSim` previously raised `ModuleNotFoundError`.
238
+ - Placeholder author name/email replaced in `pyproject.toml`.
239
+ - `heat_exchangers_old.py` (dead, never imported) removed from the package.
240
+ - `.gitignore` added; it excludes private keys, `.env` files and build output.
241
+ - `README.md` no longer contains unresolved Git merge-conflict markers.
242
+
243
+ ### Fixed — state points (`Prop`)
244
+ - `Q` is now ALWAYS numeric (0-1) or `None`. The human-readable phase label
245
+ moved to the new `Prop.phase` attribute. Previously `_classify_phase()`
246
+ overwrote `Q` with strings like `"Superheated (46.97 K)"`, which crashed
247
+ `Separator` and made any arithmetic on `Q` unsafe.
248
+ - Assigning the FIRST property to an under-defined point no longer raises
249
+ `ValueError: not enough values to unpack`; a solve is attempted only once
250
+ two independent inputs are present.
251
+ - Therminol-66 works again. `T66.json` was missing the `entropy` block that
252
+ `_calc_therminol66()` required, so every Therminol-66 state raised
253
+ `KeyError: 'entropy'`. Entropy is now integrated analytically from the Cp
254
+ polynomial (s = ∫Cp/T dT, which contains a log term and therefore cannot
255
+ be a polynomial). Whichever of (T, H) the user supplies is preserved
256
+ rather than round-tripped through the independent inverse fit, the fitted
257
+ temperature range is range-checked, and `'T66'` is accepted as an alias.
258
+
259
+ ### Fixed — turbomachinery
260
+ - `Turbine`, `Compressor` and `Pump` write the resolved mass flow back to
261
+ their ports again. Since 3.1.1 the flow was kept local and left to the
262
+ propagation graph, which is only built by `Solve()` — so the documented
263
+ `Calculate=True` workflow left every downstream point at
264
+ `Mass_flowrate=None` and the next heat exchanger could not solve.
265
+ - Mass-flow resolution uses an explicit `is not None` test, so a legitimate
266
+ 0 kg/s is no longer treated as missing.
267
+
268
+ ### Fixed — heat exchangers
269
+ - Mass flows are resolved BEFORE the SimpleHEX branch. Previously that branch
270
+ returned early, so `Hot/Cold_Mass_flowrate` were still `None` and the
271
+ solver took the wrong path.
272
+ - `_solve_simple_hex()` rewritten: each side is now closed independently
273
+ against a shared duty, so a two-sided unit no longer solves only one side;
274
+ a fully-determined unit is idempotent (calling `Cal()` twice used to raise
275
+ "insufficient inputs", which made every `Solve()` iteration report phantom
276
+ failures); and an over-specified `Q` is checked rather than ignored.
277
+ - The NTU-effectiveness solver is reachable. It was the last `elif` in a
278
+ chain that had already consumed n = 0, 1, 2 and 3, so the `effectiveness`
279
+ argument silently did nothing.
280
+ - A failed 3-unknown solve no longer corrupts the model: the anchor is
281
+ validated against the solvable-case table first and rolled back on failure,
282
+ and both valid anchors are tried before giving up.
283
+ - Anchor rules no longer compare `None` temperatures (`TypeError`), and the
284
+ `H_co → H_hi` guard tests the correct temperature pair; it previously
285
+ reused the `H_ho → H_ci` condition verbatim.
286
+ - The `{H_co, mc}` retry passes the narrowed bracket instead of re-using the
287
+ bracket that had just failed, and cold-side enthalpies are no longer
288
+ bounded by hot-side enthalpies (meaningless across different fluids).
289
+ - `exclude_idx` masking now covers the final profile index, which is exactly
290
+ the anchored point it was meant to exclude.
291
+ - `_compute_outputs()` uses `is not None` instead of truthiness, so a valid
292
+ H = 0 or Q = 0 no longer silently skips the duty and UA calculations.
293
+ - Exergy destruction is computed per connected leg, so one-sided units report
294
+ a value instead of falling back to "Not Calculated" and printing to stdout.
295
+ - Solvable-case counts in the module docstring corrected to 10 (2-unknown)
296
+ and 8 (3-unknown), matching the logic table and the CHANGELOG. The list is
297
+ now a single constant, `HeatExchanger._SOLVABLE_2_UNKNOWN`.
298
+ - `HeatExchanger` and `TES` call `super().__init__()`; `TES` reuses the
299
+ shared mass-flow resolver instead of comparing floats with `==`.
300
+
301
+ ### Fixed — model
302
+ - `ModelSummary()` skips unsolved components with a warning instead of
303
+ raising `TypeError` on `Q_in += None`.
304
+ - `Efficiency` is `nan` rather than the string `"Not Applicable"` for
305
+ refrigeration cycles, and `COP_R` / `COP_HP` are exposed as attributes.
306
+ - `Solve()` convergence uses a relative tolerance (new `tol` argument). The
307
+ old absolute 1e-6 J/kg was ~3e-13 relative on typical enthalpies.
308
+ - `Solve()` and the flow network are quiet unless `verbose=True`.
309
+ - `custom_order` defaults to `None` instead of a mutable list.
310
+ - `Point_print()` no longer advertises private attributes.
311
+
312
+ ### Fixed — sensitivity analysis
313
+ - `compute_sensitivity_indices()` returned a meaningless `sensitivity_index`.
314
+ It normalised BOTH axes to [0, 1] before taking the gradient, so any
315
+ monotonic near-linear sweep gave ~1.0000 regardless of the parameter — the
316
+ metric could not rank parameters against each other, which is the one thing
317
+ the wiki instructs users to do with it. It is now the mean **elasticity**,
318
+ S = (dY/dX)·(X/Y): dimensionless, so parameters in kelvin and pascals stay
319
+ comparable. Validation on a simple Rankine cycle now gives eta_t → 1.01
320
+ (cycle efficiency is nearly proportional to turbine efficiency, as
321
+ expected), T1 → 0.27, P_cond → 0.07. The raw slope is also returned, as
322
+ the new `mean_gradient` key.
323
+ - `ModelSummary()` accepts `verbose=True` (default). `SensitivityAnalyzer`
324
+ calls it with `verbose=False`, so a sweep no longer prints a full
325
+ energy + exergy table for every single evaluation point.
326
+
327
+ ### Fixed — components, plotting, analysis
328
+ - `Splitter` treats a known inlet flow as authoritative instead of averaging
329
+ it with stale outlet flows. Re-running a splitter with refined fractions
330
+ (as the regenerative Rankine example does) previously broke mass
331
+ conservation downstream.
332
+ - Every component `__str__` returns "not yet solved" instead of raising.
333
+ - `plot_energy_summary()` no longer raises `NameError` on `Compressor` in its
334
+ import-failure fallback, and tolerates unsolved components.
335
+ - `'Water'` removed from `INCOMPRESSIBLE_FLUIDS`; it is an ordinary
336
+ compressible CoolProp fluid and was silently losing its saturation dome.
337
+ - `plot_hex_profile()` reports a clear error instead of dividing by `None`.
338
+ - `compute_sensitivity_indices()` guards against a zero-width parameter range.
339
+ - Bare `except:` replaced with `except Exception:` throughout (22 sites).
340
+ - Corrected `thermocycle.*` import paths in docstrings to `ThermoSim.*`.
341
+
342
+ ## [3.2.0] - 2026-04-20
343
+ - Solver is now more robust.
344
+ - User can give the order of solving components.
345
+ - Now HEX can solve 10 cases with 2 unknows and 8 cases with 3 unknwons
346
+ - Mass flowrate now can automatically propgate through the branches.
347
+
348
+ ## [3.1.1] - 2026-03-31
349
+ - Fix the bug in testing.
350
+ ## [3.1.0] - 2026-03-30
351
+ - Energy-balance consistency check when all four states are known.
352
+ - Mass-flow written back after PPT/brentq solvers (was silently discarded).
353
+ - Upfront problem classifier (_classify_problem) catches zero-info,
354
+ both-m̊-unknown, and over-specified inputs before any computation.
355
+ - Silent PPT mutation (self.PPT -= 0.001) replaced with a proper
356
+ ValueError so pinch violations are never hidden.
357
+ - Phase-change guard in _solve_effectiveness(); falls back to
358
+ enthalpy-based Q when either stream is two-phase (Cp is ill-defined).
359
+ - TES over-specified branch added: CI.H, CO.H, and Capacity all known
360
+ → consistency check instead of silent fall-through.
361
+ - warnings.warn(RuntimeWarning) replaces bare print() in solver errors;
362
+ Solution_Status set False and Cal() returns cleanly on non-fatal failures.
363
+ - SimpleHEX two-sided consistency path: when both cold and hot sides are
364
+ fully given, Q from each side is compared and averaged if consistent.
365
+ - _resolve_side() raises immediately when both mass-flows are None
366
+ (was a silent None that propagated to arithmetic errors later).
367
+ - exergy sign and base-class helper preserved from v1.x bugfix baseline.
368
+ - All three Cal() methods now handle a "both-known" branch that validates
369
+ isentropic consistency and emits a warning when the supplied enthalpy
370
+ pair violates the declared efficiency.
371
+ - Pump.Cal() Incompressible branch now handles reverse (In.H is None)
372
+ and both-known sub-cases with proper guards.
373
+ - fsolve calls use physically-motivated initial guesses (rather than
374
+ the outlet enthalpy) and check convergence with full_output=True.
375
+ - Guard added in the Incompressible path for In.D being None.
376
+ - Duplicate 'from scipy.optimize import fsolve' inside Compressor.Cal()
377
+ removed.
378
+ - All three __str__ methods guard against Solution_Status=False (i.e.
379
+ Cal() was never called) so printing an unsolved component does not
380
+ raise AttributeError on self.In / self.Out.
381
+
382
+ - CSV Export Functionality
383
+
384
+ Added CSV export capability to all plotting methods
385
+
386
+ - save_csv parameter (default: False) - enables CSV export
387
+ - csv_path parameter (optional) - custom file path, auto-generates if not provided
388
+ - Auto-generated filenames include timestamp (e.g., Ts_diagram_20240115_143022.csv)
389
+ New utility methods:
390
+
391
+ - export_all_points() - Export all thermodynamic state points to CSV
392
+ - export_all_components() - Export all component performance data to CSV
393
+ - _generate_csv_filename() - Auto-generate timestamped filenames
394
+ - _save_dataframe_to_csv() - Centralized CSV saving with error handling
395
+ Enhanced Data Structure
396
+
397
+ CSV files now include:
398
+ - Original SI units (J/kg, Pa, K)
399
+ - Converted engineering units (kJ/kg, bar, °C)
400
+ - Point/component identification
401
+ - Calculated derivatives (percentages, ratios)
402
+
403
+ ## [3.0.0] - 2026-03-25
404
+
405
+ ### Added
406
+ - Modular package structure (`ThermoSIm/` package with separate files)
407
+ - `Compressor` component for gas cycles (e.g., Brayton)
408
+ - `CyclePlotter` class with T-s, P-h, h-s diagrams, saturation domes
409
+ - Exergy destruction bar chart and pie chart
410
+ - Heat exchanger temperature profile plots
411
+ - Energy flow summary visualisation
412
+ - `SensitivityAnalyzer` with single sweep, double sweep, and multi-output
413
+ - `save_model()` and `load_model()` for JSON serialisation
414
+ - Abstract `Component` base class with shared helpers
415
+ - 48+ unit tests (pytest)
416
+ - `.gitignore`, `README.md`, `CHANGELOG.md`, `setup.py`
417
+
418
+ ### Fixed
419
+ - `__init__` was misspelled as `init` (constructor never called)
420
+ - Exergy sign error in HeatExchanger and TES: was `Out_hot - Out_cold`, now `Out_hot + Out_cold`
421
+ - `ModelSummary()` crashed on second call (DataFrame append issue)
422
+ - Type checking used fragile string comparison instead of `isinstance()`
423
+ - `Solve()` silently swallowed all errors
424
+ - Multiple typos in error messages and `__str__` methods
425
+
426
+ ### Changed
427
+ - Dead state stored in `config.py` module instead of class variable
428
+ - Mass-flow resolution logic extracted to base class
429
+ - Visualisation completely separated from computation
430
+ - Iterative solver now checks convergence
431
+
432
+ ## [2.0.0] - Previous version
433
+ - Single-file module
434
+
435
+ ## [1.0.0] - Initial release
436
+ - Basic functionality
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 [Md. Waheduzzaman Basunia Nouman]
3
+ Copyright (c) 2025 [Md. Waheduzzaman Basunia Nouman]
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -4,4 +4,4 @@ include CHANGELOG.md
4
4
  include ThermoSim/T66.json
5
5
  recursive-include ThermoSim *.py *.json
6
6
  recursive-exclude tests *
7
- recursive-exclude __pycache__ *
7
+ recursive-exclude __pycache__ *
@@ -0,0 +1,185 @@
1
+ Metadata-Version: 2.4
2
+ Name: ThermoSim
3
+ Version: 3.3.0
4
+ Summary: A Python package for thermodynamic cycle modelling and analysis
5
+ Author-email: "Md. Waheduzzaman Basunia Nouman" <md.waheduzzaman.nouman@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/Nouman090/ThermoSim
8
+ Project-URL: Repository, https://github.com/Nouman090/ThermoSim
9
+ Project-URL: Issues, https://github.com/Nouman090/ThermoSim/issues
10
+ Project-URL: Changelog, https://github.com/Nouman090/ThermoSim/blob/main/CHANGELOG.md
11
+ Keywords: thermodynamics,rankine,brayton,ORC,exergy,CoolProp
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Topic :: Scientific/Engineering :: Physics
17
+ Requires-Python: >=3.9
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: CoolProp
21
+ Requires-Dist: scipy
22
+ Requires-Dist: matplotlib
23
+ Requires-Dist: numpy
24
+ Requires-Dist: pandas
25
+ Requires-Dist: tabulate
26
+ Provides-Extra: optimization
27
+ Requires-Dist: pymoo; extra == "optimization"
28
+ Provides-Extra: test
29
+ Requires-Dist: pytest; extra == "test"
30
+ Provides-Extra: all
31
+ Requires-Dist: pymoo; extra == "all"
32
+ Requires-Dist: pytest; extra == "all"
33
+ Dynamic: license-file
34
+
35
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.22016326.svg)](https://doi.org/10.5281/zenodo.22016326)
36
+ ![tests](https://github.com/Nouman090/ThermoSim/actions/workflows/tests.yml/badge.svg)
37
+ # ThermoSim
38
+
39
+ ![ThermoSim logo](https://github.com/Nouman090/ThermoSim/blob/main/docs/ThermoSim%20Logo%202.jpg?raw=true)
40
+
41
+ **ThermoSim** is a Python package for modelling and analysing thermodynamic
42
+ power cycles, built on [CoolProp](http://www.coolprop.org/) for fluid
43
+ properties.
44
+
45
+ ---
46
+
47
+ ## Features
48
+
49
+ - **State-point calculation** from any two independent properties
50
+ (P, T, H, S, Q, D) for water, refrigerants, air and every other CoolProp
51
+ fluid, plus a built-in Therminol-66 correlation
52
+ - **Components**: Turbine, Pump, Compressor, Heat Exchanger, TES, Mixer,
53
+ Splitter, Separator, Expansion Valve, Pipe, Source, Sink
54
+ - **Pinch analysis** for heat exchangers, with a generalised solver that
55
+ closes up to three unknowns from
56
+ `{H_hi, H_ho, H_ci, H_co, mh, mc}`
57
+ - **Automatic mass-flow propagation** through branches, splitters and mixers
58
+ - **Exergy analysis** against a configurable dead state
59
+ - **Cycle plots**: T-s, P-h and h-s diagrams with saturation domes, heat
60
+ exchanger temperature profiles, exergy bar/pie charts, energy summaries
61
+ - **Sensitivity analysis**: single-parameter, multi-output and 2D contour
62
+ sweeps, with CSV export
63
+ - **Save / load** model state to JSON
64
+ - **57 unit tests** (pytest)
65
+
66
+ ---
67
+
68
+ ## Installation
69
+
70
+ ```bash
71
+ pip install ThermoSim
72
+ ```
73
+
74
+ To install from source:
75
+
76
+ ```bash
77
+ git clone https://github.com/Nouman090/ThermoSim.git
78
+ cd ThermoSim
79
+ pip install -e .
80
+ ```
81
+
82
+ Optional extras: `pip install "ThermoSim[all]"` adds `pymoo` (optimisation)
83
+ and `pytest` (testing).
84
+
85
+ ---
86
+
87
+ ## Quick example
88
+
89
+ ```python
90
+ from ThermoSim import ThermodynamicModel, Turbine, Pump, HeatExchanger
91
+ from ThermoSim.plotting import CyclePlotter
92
+
93
+ Model = ThermodynamicModel()
94
+ Model.set_dead_state()
95
+
96
+ Model.add_point('water', '1', P=8e6, T=753.15, Mass_flowrate=1)
97
+ Model.add_point('water', '2', P=0.008e6)
98
+ Model.add_point('water', '3', P=0.008e6, Q=0)
99
+ Model.add_point('water', '4', P=8e6)
100
+
101
+ Turbine(Model, 'Turbine', '1', '2', n_isen=0.85, Calculate=True)
102
+ HeatExchanger(Model, 'Condenser', PPT=5, HEX_type='SimpleHEX',
103
+ HeatAdded=False, Hot_In_state='2', Hot_Out_state='3',
104
+ Cold_In_state=None, Cold_Out_state=None, Calculate=True)
105
+ Pump(Model, 'Pump', '3', '4', n_isen=1.0, Calculate=True)
106
+ HeatExchanger(Model, 'Boiler', PPT=5, HEX_type='SimpleHEX',
107
+ HeatAdded=True, Hot_In_state=None, Hot_Out_state=None,
108
+ Cold_In_state='4', Cold_Out_state='1', Calculate=True)
109
+
110
+ print(Model)
111
+
112
+ plotter = CyclePlotter(Model)
113
+ plotter.plot_Ts_diagram(['1', '2', '3', '4', '1'])
114
+ ```
115
+
116
+ For cycles with recycle loops or branches, build the components without
117
+ `Calculate=True` and let the iterative solver handle the ordering:
118
+
119
+ ```python
120
+ Model.Solve(verbose=True)
121
+ Model.ModelSummary()
122
+ ```
123
+
124
+ ---
125
+
126
+ ## Running the tests
127
+
128
+ ```bash
129
+ pytest tests/ -v
130
+ ```
131
+
132
+ ---
133
+
134
+ ## Who this is for
135
+
136
+ - **Engineering students** studying thermodynamics, energy systems and heat
137
+ transfer, who want to simulate real energy systems rather than only work
138
+ through closed-form textbook problems.
139
+ - **Researchers** modelling, optimising and analysing thermodynamic and
140
+ energy-efficiency problems.
141
+ - **Energy system designers** working on power plants, heat exchanger
142
+ networks, refrigeration cycles and renewable energy systems.
143
+
144
+ ### Typical applications
145
+
146
+ 1. **Heat exchanger design and optimisation** — double-pipe units,
147
+ evaporators and condensers, with pinch-point analysis to trade off
148
+ thermal effectiveness against surface area.
149
+ 2. **Pumps, turbines and compressors** — work output, isentropic efficiency
150
+ and energy transfer for power generation, refrigeration and HVAC.
151
+ 3. **Energy efficiency analysis** — exergy destruction accounting to locate
152
+ where useful work is actually being lost.
153
+ 4. **Thermodynamic cycle simulation** — Rankine, regenerative Rankine,
154
+ Brayton, ORC and refrigeration cycles.
155
+
156
+ ---
157
+
158
+ ## Resources
159
+
160
+ - [**PyPI package**](https://pypi.org/project/ThermoSim/)
161
+ - [**Source code**](https://github.com/Nouman090/ThermoSim)
162
+ - [**Report an issue**](https://github.com/Nouman090/ThermoSim/issues)
163
+ - [**Wiki**](https://github.com/Nouman090/ThermoSim/wiki)
164
+ - [**Changelog**](https://github.com/Nouman090/ThermoSim/blob/main/CHANGELOG.md)
165
+
166
+ ---
167
+
168
+ ## Contributing
169
+
170
+ Contributions are welcome. Fork the repository and open a pull request; for
171
+ major changes, please open an issue to discuss it first.
172
+
173
+ ---
174
+
175
+ ## License
176
+
177
+ MIT — see [LICENSE](https://github.com/Nouman090/ThermoSim/blob/main/LICENSE).
178
+
179
+ ---
180
+
181
+ ## Acknowledgements
182
+
183
+ Created and maintained by
184
+ [Md. Waheduzzaman Nouman](https://github.com/Nouman090), for educational and
185
+ research use.