operange 0.1.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 (116) hide show
  1. operange-0.1.0/LICENSE +21 -0
  2. operange-0.1.0/MANIFEST.in +7 -0
  3. operange-0.1.0/PKG-INFO +247 -0
  4. operange-0.1.0/README.md +221 -0
  5. operange-0.1.0/docs/api.md +194 -0
  6. operange-0.1.0/docs/conf.py +18 -0
  7. operange-0.1.0/docs/engineering-changes.md +143 -0
  8. operange-0.1.0/docs/failure-distance.md +174 -0
  9. operange-0.1.0/docs/index.md +177 -0
  10. operange-0.1.0/docs/linear-dispatch.md +145 -0
  11. operange-0.1.0/docs/methods.md +100 -0
  12. operange-0.1.0/docs/pinch.md +164 -0
  13. operange-0.1.0/docs/startup.md +131 -0
  14. operange-0.1.0/examples/__init__.py +1 -0
  15. operange-0.1.0/examples/affine_utility.py +95 -0
  16. operange-0.1.0/examples/coupled_design.py +654 -0
  17. operange-0.1.0/examples/engineering_changes.py +97 -0
  18. operange-0.1.0/examples/failure_distance.py +74 -0
  19. operange-0.1.0/examples/heat_recovery.py +151 -0
  20. operange-0.1.0/examples/linear_dispatch.py +121 -0
  21. operange-0.1.0/examples/pinch.py +63 -0
  22. operange-0.1.0/examples/quadratic_residopt.py +273 -0
  23. operange-0.1.0/examples/reporting.py +366 -0
  24. operange-0.1.0/examples/startup.py +78 -0
  25. operange-0.1.0/examples/steam_header.py +99 -0
  26. operange-0.1.0/examples/thermal_storage.py +172 -0
  27. operange-0.1.0/notes/README.md +126 -0
  28. operange-0.1.0/notes/affine-process-adapter.md +204 -0
  29. operange-0.1.0/notes/coupled-design-experiment.md +237 -0
  30. operange-0.1.0/notes/dsl-primitives.md +176 -0
  31. operange-0.1.0/notes/evidence-and-backends.md +184 -0
  32. operange-0.1.0/notes/heat-recovery-example.md +195 -0
  33. operange-0.1.0/notes/package-boundary.md +150 -0
  34. operange-0.1.0/notes/quadratic-residopt-experiment.md +207 -0
  35. operange-0.1.0/notes/result-contract-cases.md +201 -0
  36. operange-0.1.0/notes/result-contract.md +269 -0
  37. operange-0.1.0/notes/roadmap.md +181 -0
  38. operange-0.1.0/notes/thermal-storage-example.md +197 -0
  39. operange-0.1.0/notes/uncertainty-and-recourse.md +188 -0
  40. operange-0.1.0/notes/uncertainty-geometries.md +242 -0
  41. operange-0.1.0/pyproject.toml +52 -0
  42. operange-0.1.0/setup.cfg +4 -0
  43. operange-0.1.0/src/operange/__init__.py +136 -0
  44. operange-0.1.0/src/operange/_affine_distance.py +341 -0
  45. operange-0.1.0/src/operange/_affine_distance_results.py +282 -0
  46. operange-0.1.0/src/operange/_basic_support.py +75 -0
  47. operange-0.1.0/src/operange/_change_contracts.py +271 -0
  48. operange-0.1.0/src/operange/_finite_audit.py +91 -0
  49. operange-0.1.0/src/operange/_geometry.py +226 -0
  50. operange-0.1.0/src/operange/_heat_cascade.py +128 -0
  51. operange-0.1.0/src/operange/_linear_process_results.py +223 -0
  52. operange-0.1.0/src/operange/_linear_recourse.py +213 -0
  53. operange-0.1.0/src/operange/_numeric.py +65 -0
  54. operange-0.1.0/src/operange/_pinch_results.py +198 -0
  55. operange-0.1.0/src/operange/_reference.py +65 -0
  56. operange-0.1.0/src/operange/_startup_results.py +307 -0
  57. operange-0.1.0/src/operange/_storage_reference.py +73 -0
  58. operange-0.1.0/src/operange/adapters.py +246 -0
  59. operange-0.1.0/src/operange/affine.py +332 -0
  60. operange-0.1.0/src/operange/affine_results.py +599 -0
  61. operange-0.1.0/src/operange/changes.py +373 -0
  62. operange-0.1.0/src/operange/claim.py +234 -0
  63. operange-0.1.0/src/operange/composition.py +179 -0
  64. operange-0.1.0/src/operange/contract_types.py +583 -0
  65. operange-0.1.0/src/operange/distance.py +51 -0
  66. operange-0.1.0/src/operange/domain_io.py +68 -0
  67. operange-0.1.0/src/operange/domains.py +331 -0
  68. operange-0.1.0/src/operange/engineering_results.py +494 -0
  69. operange-0.1.0/src/operange/experimental/__init__.py +1 -0
  70. operange-0.1.0/src/operange/experimental/_quadratic_bounds.py +161 -0
  71. operange-0.1.0/src/operange/experimental/_residopt_quadratic.py +181 -0
  72. operange-0.1.0/src/operange/experimental/quadratic.py +563 -0
  73. operange-0.1.0/src/operange/geometries.py +270 -0
  74. operange-0.1.0/src/operange/heat_recovery.py +354 -0
  75. operange-0.1.0/src/operange/heat_results.py +542 -0
  76. operange-0.1.0/src/operange/heat_sensitivity.py +309 -0
  77. operange-0.1.0/src/operange/linear.py +100 -0
  78. operange-0.1.0/src/operange/linear_process.py +230 -0
  79. operange-0.1.0/src/operange/pinch.py +272 -0
  80. operange-0.1.0/src/operange/policy.py +115 -0
  81. operange-0.1.0/src/operange/polytope.py +212 -0
  82. operange-0.1.0/src/operange/primitives.py +247 -0
  83. operange-0.1.0/src/operange/profiles.py +53 -0
  84. operange-0.1.0/src/operange/recourse.py +66 -0
  85. operange-0.1.0/src/operange/reference.py +102 -0
  86. operange-0.1.0/src/operange/results.py +87 -0
  87. operange-0.1.0/src/operange/startup.py +291 -0
  88. operange-0.1.0/src/operange/storage.py +459 -0
  89. operange-0.1.0/src/operange/storage_contract.py +488 -0
  90. operange-0.1.0/src/operange/storage_results.py +58 -0
  91. operange-0.1.0/src/operange/storage_types.py +154 -0
  92. operange-0.1.0/src/operange/vector.py +101 -0
  93. operange-0.1.0/src/operange.egg-info/PKG-INFO +247 -0
  94. operange-0.1.0/src/operange.egg-info/SOURCES.txt +114 -0
  95. operange-0.1.0/src/operange.egg-info/dependency_links.txt +1 -0
  96. operange-0.1.0/src/operange.egg-info/requires.txt +5 -0
  97. operange-0.1.0/src/operange.egg-info/top_level.txt +1 -0
  98. operange-0.1.0/tests/acceptance/wheel.py +334 -0
  99. operange-0.1.0/tests/fixtures/prototype_result_v1.json +3087 -0
  100. operange-0.1.0/tests/test_affine.py +544 -0
  101. operange-0.1.0/tests/test_affine_distance.py +491 -0
  102. operange-0.1.0/tests/test_changes.py +491 -0
  103. operange-0.1.0/tests/test_compact_results.py +163 -0
  104. operange-0.1.0/tests/test_coupled_design.py +197 -0
  105. operange-0.1.0/tests/test_dsl.py +482 -0
  106. operange-0.1.0/tests/test_examples.py +168 -0
  107. operange-0.1.0/tests/test_geometries.py +527 -0
  108. operange-0.1.0/tests/test_heat_recovery.py +371 -0
  109. operange-0.1.0/tests/test_linear_process.py +390 -0
  110. operange-0.1.0/tests/test_numerical_regressions.py +246 -0
  111. operange-0.1.0/tests/test_pinch.py +519 -0
  112. operange-0.1.0/tests/test_quadratic_experiment.py +329 -0
  113. operange-0.1.0/tests/test_release_boundary.py +84 -0
  114. operange-0.1.0/tests/test_result_contract.py +507 -0
  115. operange-0.1.0/tests/test_startup.py +359 -0
  116. operange-0.1.0/tests/test_storage.py +333 -0
operange-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 updatesupport contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,7 @@
1
+ include LICENSE README.md pyproject.toml
2
+ recursive-include tests *.py *.json
3
+ recursive-include examples *.py
4
+ recursive-include docs *.md *.py
5
+ recursive-include notes *.md
6
+ prune notes/private
7
+ global-exclude *.py[cod]
@@ -0,0 +1,247 @@
1
+ Metadata-Version: 2.4
2
+ Name: operange
3
+ Version: 0.1.0
4
+ Summary: Process-engineering sensitivity and robustness DSL with portable evidence
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/nahuaque/operange
7
+ Project-URL: Repository, https://github.com/nahuaque/operange
8
+ Project-URL: Documentation, https://nahuaque.github.io/operange/
9
+ Project-URL: Issues, https://github.com/nahuaque/operange/issues
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Scientific/Engineering
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: numpy>=2.2.6
22
+ Requires-Dist: scipy>=1.15.3
23
+ Provides-Extra: cvxpy
24
+ Requires-Dist: cvxpy>=1.5; extra == "cvxpy"
25
+ Dynamic: license-file
26
+
27
+ # Operange
28
+
29
+ **Check whether a process can keep meeting its requirements as operating
30
+ conditions change, and identify what limits its flexibility.**
31
+
32
+ `operange` is a Python library for process engineers studying
33
+ uncertain loads, feed conditions, equipment capacities and operating decisions.
34
+ It provides a small engineering language for specifying a robustness study and
35
+ returning process values, sensitivities, failing conditions and the evidence
36
+ behind a verdict.
37
+
38
+ **Status:** alpha, developed as an independent project.
39
+ The implemented models are deliberately bounded; current capabilities are
40
+ listed below. The distribution is independent of `updatesupport` and requires
41
+ Python 3.10+, NumPy and SciPy. The development version is `0.1.0`.
42
+
43
+ ## Install from this checkout
44
+
45
+ ```bash
46
+ python -m pip install .
47
+ ```
48
+
49
+ Or build a wheel from the repository root and install it:
50
+
51
+ ```bash
52
+ uv build --wheel --out-dir dist
53
+ python -m pip install dist/operange-0.1.0-py3-none-any.whl
54
+ ```
55
+
56
+ ## Try the shared-steam example
57
+
58
+ ```python
59
+ from operange import (
60
+ AffineOutput, AffineProcessAdapter, AffineRequirement, AffineTerm,
61
+ BoxSet, BudgetSet, Parameter,
62
+ )
63
+
64
+ loads = BoxSet((
65
+ Parameter("dryer", "MW", 10, 8, 12, 2, "Synthetic dryer envelope"),
66
+ Parameter("evaporator", "MW", 6, 4, 8, 2, "Synthetic evaporator envelope"),
67
+ ))
68
+ model = AffineProcessAdapter(
69
+ name="Shared steam header",
70
+ input_space=loads.space,
71
+ outputs=(AffineOutput(
72
+ "steam", "MW", "thermal_power", 0,
73
+ tuple(AffineTerm(n, 1, "MW/MW") for n in loads.space.names),
74
+ "Sum of process steam loads",
75
+ ),),
76
+ requirements=(AffineRequirement("header_capacity", "steam", 19),),
77
+ )
78
+ claim = model.as_claim(loads)
79
+ print(claim.evaluate_result(loads.nominal).to_json())
80
+ print(claim.sensitivity_result(loads.nominal).to_json())
81
+ print(claim.audit_result().payload.verdict) # fail: peak demand is 20 MW
82
+ print(model.as_claim(BudgetSet(loads, 1)).audit_result().payload.verdict) # pass
83
+ ```
84
+
85
+ Nominal steam demand is 16 MW. The box permits both users to peak at once;
86
+ the budget allows only one full normalized deviation in total. Changing that
87
+ assumption creates a different claim, not an equipment repair.
88
+
89
+ The [getting-started guide](docs/index.md)
90
+ extends this model with a shared boiler, evidence inspection, an equipment
91
+ change and JSON round trips. The complete consumer example is
92
+ [`examples/steam_header.py`](examples/steam_header.py).
93
+
94
+ ## What works today
95
+
96
+ | Model | Implemented questions | Scope |
97
+ | --- | --- | --- |
98
+ | Caller-declared affine model: constant plus weighted inputs and fixed controls | Point evaluation, analytical first sensitivities, requirement audits and boundary/positive-violation distances | Audits over supported uncertainty domains; distance searches over boxes and polytopes with explicit normalized L-infinity scales; controls remain fixed |
99
+ | Caller-declared linear model with bounded controls | Joint dispatch feasibility, finite-scenario audits and checked infeasibility witnesses | Fixed or fully observed single-stage operation; coupled equipment limits always apply; no dispatch derivatives or distance searches |
100
+ | Heat-recovery reference model | Heat-delivery evaluation, optimized-response sensitivities, full-box audits, feasibility boundary, positive-shortfall breaking search and engineering-change comparison | Static, constant COP, two uncertain inputs and fully observed electrical-power recourse |
101
+ | Thermal-storage reference model | Fixed, causal and perfect-foresight audits; conflicting futures; equipment and earlier-information changes | Complete declared finite two-period tree; no general trajectory solver or sensitivity operator |
102
+ | Supplied startup profiles | Peak and integrated load, shared-capacity audits and comparison of fixed start schedules | Finite amplitude/duration/timing scenarios; all time segments of the declared piecewise-linear profiles; no motor dynamics or adaptive scheduler |
103
+ | Heat-cascade reference model | Minimum heating/cooling targets, maximum heat recovery, pinch locations and utility-target audits | Finite steady-state sensible-heat scenarios at fixed ΔTmin; no installed exchanger-network feasibility or sensitivity operator |
104
+
105
+ The [linear dispatch guide](docs/linear-dispatch.md) adds two adjustable boilers
106
+ and a shared fuel supply. Redispatch handles either individual process-load peak;
107
+ the combined peak remains infeasible. Increasing the shared fuel capacity passes
108
+ all four declared scenarios. Each pass includes checked physical controls, and
109
+ each failure includes a constraint combination that rules out all permitted
110
+ controls. Solver termination alone cannot establish either conclusion.
111
+
112
+ The [failure-distance guide](docs/failure-distance.md) finds which fixed affine
113
+ requirement first reaches its limit. The shared steam header reaches its boundary
114
+ at normalized distance 0.75; a 0.01 MW violation requires distance 0.7525.
115
+ Distances carry verified lower and upper bounds, and an unresolved branch cannot
116
+ silently disappear from the search for the nearest failure.
117
+
118
+ The [engineering-change guide](docs/engineering-changes.md) uses one common API
119
+ to re-audit named candidate claims. It distinguishes equipment and operating
120
+ changes from revised domains or service requirements, retaining full audits,
121
+ witnesses and observed margins in a portable comparison. A passing candidate
122
+ is labelled `restored` only when it preserves the original commitment.
123
+
124
+ The [startup example](docs/startup.md)
125
+ keeps a compressor start distinct from ordinary load variation. Two synthetic
126
+ starts reach 700 kVA across the 65 declared scenarios; staggering the commands
127
+ reduces that maximum to 480 kVA against a 500 kVA limit. The uncertainty and
128
+ equipment remain unchanged. `PiecewiseLinearProfile` makes interpolation and
129
+ the running tail explicit, so the audit checks between supplied knots as well
130
+ as at them. Coverage does not extend to unmeasured waveform spikes or uncertain
131
+ values between the finite scenarios.
132
+
133
+ The [heat-cascade example](docs/pinch.md)
134
+ screens heat-integration targets across four steady operating cases. Nominal
135
+ heating needs 10 kW; reduced hot-stream flow or increased cold-stream flow each
136
+ raise it to 40 kW, while both together require 100 kW and exceed a 50 kW limit.
137
+ The pinch location changes between scenarios. A passing target screen does not
138
+ prove an installed exchanger network works: connections, area, fouling and
139
+ controls require a network model. Heat-cascade data are returned for downstream
140
+ use without plotting or economics in the package.
141
+
142
+ Uncertainty declarations include independent bounds (`BoxSet`), explicit finite
143
+ cases, fractions summing to one (`SimplexSet`), budgets limiting simultaneous
144
+ deviations, ellipsoids and linear coupled restrictions (`PolytopeSet`). Named
145
+ vector inputs and domain composition are also available. Representing a domain
146
+ does not automatically give every model an optimizer or a proof over it.
147
+
148
+ The intended model boundary can cover a coupled system, as the shared-utility
149
+ example does. Current implementations do not establish whole-plant scalability
150
+ or arbitrary nonlinear flowsheet robustness. External simulator adapters,
151
+ general dynamics, degradation, faults, model ensembles and distributional
152
+ ambiguity remain future work. Your model supplies the equations and consistent
153
+ units; the package does not supply thermodynamic property packages or automatic
154
+ unit conversion.
155
+
156
+ ## The engineering question
157
+
158
+ Suppose a dryer and an evaporator share a steam header rated for 19 MW.
159
+ Their nominal demands are 10 MW and 6 MW, and each can vary by 2 MW.
160
+
161
+ | Operating condition | Steam demand | Within 19 MW capacity? |
162
+ | --- | --- | --- |
163
+ | Nominal production | 16 MW | Yes |
164
+ | Dryer at maximum, evaporator nominal | 18 MW | Yes |
165
+ | Evaporator at maximum, dryer nominal | 18 MW | Yes |
166
+ | Both at maximum | 20 MW | No |
167
+
168
+ Both individual demand increases fit. The combined increase does not. An audit
169
+ of the full declared load envelope finds this failing combination and identifies
170
+ the shared capacity constraint.
171
+
172
+ There are different engineering responses. A larger header can accommodate the
173
+ original load envelope. A scheduling restriction can prevent the two peaks
174
+ from coinciding, but that changes the operating commitment. Reducing required
175
+ production changes the service requirement. Keeping those distinctions explicit
176
+ is central to the package.
177
+
178
+ The same workflow applies to a supported model of heat recovery or storage:
179
+ which disturbance causes a requirement to fail, can permitted operating
180
+ adjustments recover it, and does a proposed engineering change withstand the
181
+ whole original envelope?
182
+
183
+ ## What you specify, and what you get back
184
+
185
+ A study makes five things explicit:
186
+
187
+ 1. **The process model and design.** Balances, capacities and calculated outputs,
188
+ such as steam demand, heat delivery or stored energy.
189
+ 2. **What can vary together.** Bounds or coupled restrictions on uncertain
190
+ inputs, with units, nominal values and the source of those assumptions.
191
+ 3. **What operators may adjust.** The allowed controls and when the information
192
+ needed to choose them becomes available.
193
+ 4. **What must remain true.** Named engineering requirements, such as meeting
194
+ heat demand or staying within a shared utility capacity.
195
+ 5. **The question to evaluate.** A particular operating point, a sensitivity,
196
+ an audit of the declared envelope, or a supported search for a nearby failure.
197
+
198
+ The outputs are structured engineering results for your own code to consume:
199
+ physical quantities and constraint margins; derivatives with their units and
200
+ operating assumptions; pass, fail or inconclusive audit outcomes; and witnesses,
201
+ bounds and coverage explaining what was established. A **witness** is a concrete
202
+ admissible condition, or a conflicting set of futures, that demonstrates failure
203
+ under the declared operating permissions.
204
+
205
+ A failed numerical solve is kept separate from physical infeasibility. A pass
206
+ over an explicitly finite set of scenarios has that finite scope. A nearest
207
+ failure is reported as such only when the search evidence supports it. Numerical
208
+ tolerances remain part of the result.
209
+
210
+ ## Public API and evidence scope
211
+
212
+ - `operange`: shared DSL, uncertainty geometry, fixed affine model
213
+ adapter and typed `process_result/v1` results.
214
+ - `operange.reference`: bounded heat-recovery and two-period
215
+ storage models, supplied-startup profile audits, heat-cascade target audits,
216
+ and model-specific engineering changes and numerical records.
217
+ - `operange.experimental`: opt-in research APIs without a stability
218
+ promise. The residopt experiment is parked and adds no runtime dependency.
219
+
220
+ See the [API and capability guide](docs/api.md)
221
+ for supported declarations, result fields and model-specific restrictions.
222
+ Unsupported operations return explicit statuses rather than an inferred pass.
223
+
224
+ ## Consumer examples
225
+
226
+ From the repository root:
227
+
228
+ ```bash
229
+ uv run python -m examples.steam_header
230
+ uv run python -m examples.heat_recovery
231
+ uv run python -m examples.thermal_storage
232
+ uv run python -m examples.startup
233
+ uv run python -m examples.pinch
234
+ ```
235
+
236
+ These repository examples own Markdown, prices, ranking and selected-repair
237
+ logic. They are not installed with the process wheel. Core repair comparisons
238
+ return engineering audits in caller order and do not select an investment.
239
+
240
+ Engineering results use `process_result/v1`. Saved result bundles and frozen
241
+ static heat contracts are tested against the installed wheel.
242
+
243
+ ## Methods and background
244
+
245
+ See [methods, scope and design lineage](docs/methods.md) for the connections to
246
+ classical flexibility analysis, robust optimization, sensitivity methods,
247
+ downstream techno-economic analysis and the original `updatesupport` extraction.
@@ -0,0 +1,221 @@
1
+ # Operange
2
+
3
+ **Check whether a process can keep meeting its requirements as operating
4
+ conditions change, and identify what limits its flexibility.**
5
+
6
+ `operange` is a Python library for process engineers studying
7
+ uncertain loads, feed conditions, equipment capacities and operating decisions.
8
+ It provides a small engineering language for specifying a robustness study and
9
+ returning process values, sensitivities, failing conditions and the evidence
10
+ behind a verdict.
11
+
12
+ **Status:** alpha, developed as an independent project.
13
+ The implemented models are deliberately bounded; current capabilities are
14
+ listed below. The distribution is independent of `updatesupport` and requires
15
+ Python 3.10+, NumPy and SciPy. The development version is `0.1.0`.
16
+
17
+ ## Install from this checkout
18
+
19
+ ```bash
20
+ python -m pip install .
21
+ ```
22
+
23
+ Or build a wheel from the repository root and install it:
24
+
25
+ ```bash
26
+ uv build --wheel --out-dir dist
27
+ python -m pip install dist/operange-0.1.0-py3-none-any.whl
28
+ ```
29
+
30
+ ## Try the shared-steam example
31
+
32
+ ```python
33
+ from operange import (
34
+ AffineOutput, AffineProcessAdapter, AffineRequirement, AffineTerm,
35
+ BoxSet, BudgetSet, Parameter,
36
+ )
37
+
38
+ loads = BoxSet((
39
+ Parameter("dryer", "MW", 10, 8, 12, 2, "Synthetic dryer envelope"),
40
+ Parameter("evaporator", "MW", 6, 4, 8, 2, "Synthetic evaporator envelope"),
41
+ ))
42
+ model = AffineProcessAdapter(
43
+ name="Shared steam header",
44
+ input_space=loads.space,
45
+ outputs=(AffineOutput(
46
+ "steam", "MW", "thermal_power", 0,
47
+ tuple(AffineTerm(n, 1, "MW/MW") for n in loads.space.names),
48
+ "Sum of process steam loads",
49
+ ),),
50
+ requirements=(AffineRequirement("header_capacity", "steam", 19),),
51
+ )
52
+ claim = model.as_claim(loads)
53
+ print(claim.evaluate_result(loads.nominal).to_json())
54
+ print(claim.sensitivity_result(loads.nominal).to_json())
55
+ print(claim.audit_result().payload.verdict) # fail: peak demand is 20 MW
56
+ print(model.as_claim(BudgetSet(loads, 1)).audit_result().payload.verdict) # pass
57
+ ```
58
+
59
+ Nominal steam demand is 16 MW. The box permits both users to peak at once;
60
+ the budget allows only one full normalized deviation in total. Changing that
61
+ assumption creates a different claim, not an equipment repair.
62
+
63
+ The [getting-started guide](docs/index.md)
64
+ extends this model with a shared boiler, evidence inspection, an equipment
65
+ change and JSON round trips. The complete consumer example is
66
+ [`examples/steam_header.py`](examples/steam_header.py).
67
+
68
+ ## What works today
69
+
70
+ | Model | Implemented questions | Scope |
71
+ | --- | --- | --- |
72
+ | Caller-declared affine model: constant plus weighted inputs and fixed controls | Point evaluation, analytical first sensitivities, requirement audits and boundary/positive-violation distances | Audits over supported uncertainty domains; distance searches over boxes and polytopes with explicit normalized L-infinity scales; controls remain fixed |
73
+ | Caller-declared linear model with bounded controls | Joint dispatch feasibility, finite-scenario audits and checked infeasibility witnesses | Fixed or fully observed single-stage operation; coupled equipment limits always apply; no dispatch derivatives or distance searches |
74
+ | Heat-recovery reference model | Heat-delivery evaluation, optimized-response sensitivities, full-box audits, feasibility boundary, positive-shortfall breaking search and engineering-change comparison | Static, constant COP, two uncertain inputs and fully observed electrical-power recourse |
75
+ | Thermal-storage reference model | Fixed, causal and perfect-foresight audits; conflicting futures; equipment and earlier-information changes | Complete declared finite two-period tree; no general trajectory solver or sensitivity operator |
76
+ | Supplied startup profiles | Peak and integrated load, shared-capacity audits and comparison of fixed start schedules | Finite amplitude/duration/timing scenarios; all time segments of the declared piecewise-linear profiles; no motor dynamics or adaptive scheduler |
77
+ | Heat-cascade reference model | Minimum heating/cooling targets, maximum heat recovery, pinch locations and utility-target audits | Finite steady-state sensible-heat scenarios at fixed ΔTmin; no installed exchanger-network feasibility or sensitivity operator |
78
+
79
+ The [linear dispatch guide](docs/linear-dispatch.md) adds two adjustable boilers
80
+ and a shared fuel supply. Redispatch handles either individual process-load peak;
81
+ the combined peak remains infeasible. Increasing the shared fuel capacity passes
82
+ all four declared scenarios. Each pass includes checked physical controls, and
83
+ each failure includes a constraint combination that rules out all permitted
84
+ controls. Solver termination alone cannot establish either conclusion.
85
+
86
+ The [failure-distance guide](docs/failure-distance.md) finds which fixed affine
87
+ requirement first reaches its limit. The shared steam header reaches its boundary
88
+ at normalized distance 0.75; a 0.01 MW violation requires distance 0.7525.
89
+ Distances carry verified lower and upper bounds, and an unresolved branch cannot
90
+ silently disappear from the search for the nearest failure.
91
+
92
+ The [engineering-change guide](docs/engineering-changes.md) uses one common API
93
+ to re-audit named candidate claims. It distinguishes equipment and operating
94
+ changes from revised domains or service requirements, retaining full audits,
95
+ witnesses and observed margins in a portable comparison. A passing candidate
96
+ is labelled `restored` only when it preserves the original commitment.
97
+
98
+ The [startup example](docs/startup.md)
99
+ keeps a compressor start distinct from ordinary load variation. Two synthetic
100
+ starts reach 700 kVA across the 65 declared scenarios; staggering the commands
101
+ reduces that maximum to 480 kVA against a 500 kVA limit. The uncertainty and
102
+ equipment remain unchanged. `PiecewiseLinearProfile` makes interpolation and
103
+ the running tail explicit, so the audit checks between supplied knots as well
104
+ as at them. Coverage does not extend to unmeasured waveform spikes or uncertain
105
+ values between the finite scenarios.
106
+
107
+ The [heat-cascade example](docs/pinch.md)
108
+ screens heat-integration targets across four steady operating cases. Nominal
109
+ heating needs 10 kW; reduced hot-stream flow or increased cold-stream flow each
110
+ raise it to 40 kW, while both together require 100 kW and exceed a 50 kW limit.
111
+ The pinch location changes between scenarios. A passing target screen does not
112
+ prove an installed exchanger network works: connections, area, fouling and
113
+ controls require a network model. Heat-cascade data are returned for downstream
114
+ use without plotting or economics in the package.
115
+
116
+ Uncertainty declarations include independent bounds (`BoxSet`), explicit finite
117
+ cases, fractions summing to one (`SimplexSet`), budgets limiting simultaneous
118
+ deviations, ellipsoids and linear coupled restrictions (`PolytopeSet`). Named
119
+ vector inputs and domain composition are also available. Representing a domain
120
+ does not automatically give every model an optimizer or a proof over it.
121
+
122
+ The intended model boundary can cover a coupled system, as the shared-utility
123
+ example does. Current implementations do not establish whole-plant scalability
124
+ or arbitrary nonlinear flowsheet robustness. External simulator adapters,
125
+ general dynamics, degradation, faults, model ensembles and distributional
126
+ ambiguity remain future work. Your model supplies the equations and consistent
127
+ units; the package does not supply thermodynamic property packages or automatic
128
+ unit conversion.
129
+
130
+ ## The engineering question
131
+
132
+ Suppose a dryer and an evaporator share a steam header rated for 19 MW.
133
+ Their nominal demands are 10 MW and 6 MW, and each can vary by 2 MW.
134
+
135
+ | Operating condition | Steam demand | Within 19 MW capacity? |
136
+ | --- | --- | --- |
137
+ | Nominal production | 16 MW | Yes |
138
+ | Dryer at maximum, evaporator nominal | 18 MW | Yes |
139
+ | Evaporator at maximum, dryer nominal | 18 MW | Yes |
140
+ | Both at maximum | 20 MW | No |
141
+
142
+ Both individual demand increases fit. The combined increase does not. An audit
143
+ of the full declared load envelope finds this failing combination and identifies
144
+ the shared capacity constraint.
145
+
146
+ There are different engineering responses. A larger header can accommodate the
147
+ original load envelope. A scheduling restriction can prevent the two peaks
148
+ from coinciding, but that changes the operating commitment. Reducing required
149
+ production changes the service requirement. Keeping those distinctions explicit
150
+ is central to the package.
151
+
152
+ The same workflow applies to a supported model of heat recovery or storage:
153
+ which disturbance causes a requirement to fail, can permitted operating
154
+ adjustments recover it, and does a proposed engineering change withstand the
155
+ whole original envelope?
156
+
157
+ ## What you specify, and what you get back
158
+
159
+ A study makes five things explicit:
160
+
161
+ 1. **The process model and design.** Balances, capacities and calculated outputs,
162
+ such as steam demand, heat delivery or stored energy.
163
+ 2. **What can vary together.** Bounds or coupled restrictions on uncertain
164
+ inputs, with units, nominal values and the source of those assumptions.
165
+ 3. **What operators may adjust.** The allowed controls and when the information
166
+ needed to choose them becomes available.
167
+ 4. **What must remain true.** Named engineering requirements, such as meeting
168
+ heat demand or staying within a shared utility capacity.
169
+ 5. **The question to evaluate.** A particular operating point, a sensitivity,
170
+ an audit of the declared envelope, or a supported search for a nearby failure.
171
+
172
+ The outputs are structured engineering results for your own code to consume:
173
+ physical quantities and constraint margins; derivatives with their units and
174
+ operating assumptions; pass, fail or inconclusive audit outcomes; and witnesses,
175
+ bounds and coverage explaining what was established. A **witness** is a concrete
176
+ admissible condition, or a conflicting set of futures, that demonstrates failure
177
+ under the declared operating permissions.
178
+
179
+ A failed numerical solve is kept separate from physical infeasibility. A pass
180
+ over an explicitly finite set of scenarios has that finite scope. A nearest
181
+ failure is reported as such only when the search evidence supports it. Numerical
182
+ tolerances remain part of the result.
183
+
184
+ ## Public API and evidence scope
185
+
186
+ - `operange`: shared DSL, uncertainty geometry, fixed affine model
187
+ adapter and typed `process_result/v1` results.
188
+ - `operange.reference`: bounded heat-recovery and two-period
189
+ storage models, supplied-startup profile audits, heat-cascade target audits,
190
+ and model-specific engineering changes and numerical records.
191
+ - `operange.experimental`: opt-in research APIs without a stability
192
+ promise. The residopt experiment is parked and adds no runtime dependency.
193
+
194
+ See the [API and capability guide](docs/api.md)
195
+ for supported declarations, result fields and model-specific restrictions.
196
+ Unsupported operations return explicit statuses rather than an inferred pass.
197
+
198
+ ## Consumer examples
199
+
200
+ From the repository root:
201
+
202
+ ```bash
203
+ uv run python -m examples.steam_header
204
+ uv run python -m examples.heat_recovery
205
+ uv run python -m examples.thermal_storage
206
+ uv run python -m examples.startup
207
+ uv run python -m examples.pinch
208
+ ```
209
+
210
+ These repository examples own Markdown, prices, ranking and selected-repair
211
+ logic. They are not installed with the process wheel. Core repair comparisons
212
+ return engineering audits in caller order and do not select an investment.
213
+
214
+ Engineering results use `process_result/v1`. Saved result bundles and frozen
215
+ static heat contracts are tested against the installed wheel.
216
+
217
+ ## Methods and background
218
+
219
+ See [methods, scope and design lineage](docs/methods.md) for the connections to
220
+ classical flexibility analysis, robust optimization, sensitivity methods,
221
+ downstream techno-economic analysis and the original `updatesupport` extraction.