fullplot 0.1.1__tar.gz → 2.0.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 (54) hide show
  1. {fullplot-0.1.1 → fullplot-2.0.0}/CHANGELOG.md +9 -0
  2. {fullplot-0.1.1 → fullplot-2.0.0}/PKG-INFO +227 -5
  3. {fullplot-0.1.1 → fullplot-2.0.0}/README.md +223 -2
  4. fullplot-2.0.0/examples/linearization/0generate_linearization_data.py +46 -0
  5. fullplot-2.0.0/examples/linearization/1inspect_linearization.py +29 -0
  6. fullplot-2.0.0/examples/linearization/2read_transient_points.py +32 -0
  7. fullplot-2.0.0/examples/linearization/LINEARIZATION.md +27 -0
  8. {fullplot-0.1.1 → fullplot-2.0.0}/pyproject.toml +7 -2
  9. {fullplot-0.1.1 → fullplot-2.0.0}/pyproject.toml.orig +7 -2
  10. {fullplot-0.1.1 → fullplot-2.0.0}/src/fullplot/__init__.py +5 -11
  11. {fullplot-0.1.1 → fullplot-2.0.0}/src/fullplot/fullplot.py +29 -0
  12. fullplot-2.0.0/src/fullplot/linearization.py +335 -0
  13. {fullplot-0.1.1 → fullplot-2.0.0}/src/fullplot/maps.py +15 -0
  14. {fullplot-0.1.1 → fullplot-2.0.0}/LICENSE +0 -0
  15. {fullplot-0.1.1 → fullplot-2.0.0}/THIRD_PARTY_LICENSES.md +0 -0
  16. {fullplot-0.1.1 → fullplot-2.0.0}/examples/hdf5_plotting/0generate_plotting_data.py +0 -0
  17. {fullplot-0.1.1 → fullplot-2.0.0}/examples/hdf5_plotting/10multidimensional_slices.py +0 -0
  18. {fullplot-0.1.1 → fullplot-2.0.0}/examples/hdf5_plotting/11show_multiple_figures.py +0 -0
  19. {fullplot-0.1.1 → fullplot-2.0.0}/examples/hdf5_plotting/12light_theme.py +0 -0
  20. {fullplot-0.1.1 → fullplot-2.0.0}/examples/hdf5_plotting/13module_level_api.py +0 -0
  21. {fullplot-0.1.1 → fullplot-2.0.0}/examples/hdf5_plotting/14save_formats.py +0 -0
  22. {fullplot-0.1.1 → fullplot-2.0.0}/examples/hdf5_plotting/1inspect_hdf5.py +0 -0
  23. {fullplot-0.1.1 → fullplot-2.0.0}/examples/hdf5_plotting/2single_trace.py +0 -0
  24. {fullplot-0.1.1 → fullplot-2.0.0}/examples/hdf5_plotting/3multiple_traces.py +0 -0
  25. {fullplot-0.1.1 → fullplot-2.0.0}/examples/hdf5_plotting/4dual_axis.py +0 -0
  26. {fullplot-0.1.1 → fullplot-2.0.0}/examples/hdf5_plotting/5heatmap_2d_dataset.py +0 -0
  27. {fullplot-0.1.1 → fullplot-2.0.0}/examples/hdf5_plotting/6heatmap_stacked_1d.py +0 -0
  28. {fullplot-0.1.1 → fullplot-2.0.0}/examples/hdf5_plotting/7log_axes.py +0 -0
  29. {fullplot-0.1.1 → fullplot-2.0.0}/examples/hdf5_plotting/8log_color_map.py +0 -0
  30. {fullplot-0.1.1 → fullplot-2.0.0}/examples/hdf5_plotting/9multidimensional_line_traces.py +0 -0
  31. {fullplot-0.1.1 → fullplot-2.0.0}/examples/hdf5_plotting/PLOTTING.md +0 -0
  32. {fullplot-0.1.1 → fullplot-2.0.0}/examples/maps/1_generate_map.py +0 -0
  33. {fullplot-0.1.1 → fullplot-2.0.0}/examples/maps/2_axis_types.py +0 -0
  34. {fullplot-0.1.1 → fullplot-2.0.0}/examples/maps/3_metadata_and_outputs.py +0 -0
  35. {fullplot-0.1.1 → fullplot-2.0.0}/examples/maps/4_hdf5_layout.py +0 -0
  36. {fullplot-0.1.1 → fullplot-2.0.0}/examples/maps/MAPS.md +0 -0
  37. {fullplot-0.1.1 → fullplot-2.0.0}/examples/traces/0_generate_trace_demo_file.py +0 -0
  38. {fullplot-0.1.1 → fullplot-2.0.0}/examples/traces/10_windowed_trace_keeps_full_time.py +0 -0
  39. {fullplot-0.1.1 → fullplot-2.0.0}/examples/traces/11_shared_time_axis_window.py +0 -0
  40. {fullplot-0.1.1 → fullplot-2.0.0}/examples/traces/1_quick_hdf5_trace_plot.py +0 -0
  41. {fullplot-0.1.1 → fullplot-2.0.0}/examples/traces/2_extract_trace_and_filter.py +0 -0
  42. {fullplot-0.1.1 → fullplot-2.0.0}/examples/traces/3_create_redlines_bluelines.py +0 -0
  43. {fullplot-0.1.1 → fullplot-2.0.0}/examples/traces/4_command_and_sequence_traces.py +0 -0
  44. {fullplot-0.1.1 → fullplot-2.0.0}/examples/traces/5_trace_math_resample_error.py +0 -0
  45. {fullplot-0.1.1 → fullplot-2.0.0}/examples/traces/6_window_scale_offset_derivative.py +0 -0
  46. {fullplot-0.1.1 → fullplot-2.0.0}/examples/traces/7_save_processed_traces.py +0 -0
  47. {fullplot-0.1.1 → fullplot-2.0.0}/examples/traces/8_missing_nan_values_create_gaps.py +0 -0
  48. {fullplot-0.1.1 → fullplot-2.0.0}/examples/traces/9_shared_time_axis_alignment.py +0 -0
  49. {fullplot-0.1.1 → fullplot-2.0.0}/examples/traces/TRACES.md +0 -0
  50. {fullplot-0.1.1 → fullplot-2.0.0}/examples/traces/_trace_demo_data.py +0 -0
  51. {fullplot-0.1.1 → fullplot-2.0.0}/src/fullplot/cli.py +0 -0
  52. {fullplot-0.1.1 → fullplot-2.0.0}/src/fullplot/hdf5.py +0 -0
  53. {fullplot-0.1.1 → fullplot-2.0.0}/src/fullplot/themes.py +0 -0
  54. {fullplot-0.1.1 → fullplot-2.0.0}/tests/test_imports.py +0 -0
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.0.0
4
+
5
+ ### Added
6
+
7
+ * Added `H5File.linearization()` to read previously saved FullFlow steady-state and transient state-space results without installing or running FullFlow.
8
+ * Added `H5File.linearizations()` to discover available networks, solver runs, base operating points, and saved transient point times.
9
+ * Added the public `Linearization` data class with `A`, `B`, `C`, `D`, baseline vectors, state/input/output labels, operating time, diagnostics, model-option metadata, and source identifiers.
10
+ * Added single-time and multiple-time transient selection through `time=` and `times=`. Multiple times return a dictionary keyed by requested time.
11
+
3
12
  ## 0.1.1
4
13
 
5
14
  ### Added
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fullplot
3
- Version: 0.1.1
4
- Summary: A documented HDF5 plotting, trace-processing, and map-generation package for engineering simulation and test data.
5
- Keywords: hdf5,plotting,engineering,simulation,test-data,map-generation,scientific-computing
3
+ Version: 2.0.0
4
+ Summary: HDF5 plotting, trace processing, linearization reading, and map generation for engineering data.
5
+ Keywords: hdf5,plotting,engineering,simulation,test-data,linearization,state-space,map-generation,scientific-computing
6
6
  Author: Saaketh Ramoju
7
7
  Author-email: Saaketh Ramoju <skramoju@gmail.com>
8
8
  License-Expression: GPL-3.0-only
@@ -30,6 +30,7 @@ Project-URL: Issues, https://github.com/saakethramoju/FullPlot/issues
30
30
  Project-URL: Changelog, https://github.com/saakethramoju/FullPlot/blob/main/CHANGELOG.md
31
31
  Project-URL: Documentation, https://github.com/saakethramoju/FullPlot#readme
32
32
  Project-URL: PyPI, https://pypi.org/project/fullplot/
33
+ Provides-Extra: examples
33
34
  Description-Content-Type: text/markdown
34
35
 
35
36
  # FullPlot
@@ -193,6 +194,220 @@ settings = run.values("metadata")
193
194
 
194
195
  ---
195
196
 
197
+ ## Reading FullFlow linearizations
198
+
199
+ FullPlot can read **previously calculated** FullFlow steady-state and transient linearization records. The result is a labeled `Linearization` object containing the state-space matrices and the operating point at which they were computed.
200
+
201
+ A continuous-time linear model takes the form
202
+
203
+ ```math
204
+ \Delta\dot{x}=A\Delta x+B\Delta u
205
+ ```
206
+
207
+ ```math
208
+ \Delta y=C\Delta x+D\Delta u
209
+ ```
210
+
211
+ where:
212
+
213
+ - `x`: physical dynamic state vector (for example stored mass or energy),
214
+ - `u`: selected input vector (for example valve area),
215
+ - `y`: selected output vector (for example chamber pressure),
216
+ - `A`: how state changes affect the state derivatives,
217
+ - `B`: how input changes affect the state derivatives,
218
+ - `C`: how state changes affect outputs,
219
+ - `D`: how input changes directly affect outputs.
220
+
221
+ `\Delta` denotes a deviation from the saved operating point. For a non-equilibrium transient point, the model also needs the saved baseline derivative:
222
+
223
+ ```math
224
+ \dot{x}\approx\dot{x}_0+A(x-x_0)+B(u-u_0)
225
+ ```
226
+
227
+ ```math
228
+ y\approx y_0+C(x-x_0)+D(u-u_0)
229
+ ```
230
+
231
+ FullPlot **does not create these matrices**. It reads them from FullFlow's linearization HDF5 output, without needing the original nonlinear network or property-model packages.
232
+
233
+ ### Discover available results
234
+
235
+ ```python
236
+ import fullplot as fplt
237
+
238
+ file = fplt.open("Linearization.h5")
239
+ info = file.linearizations()
240
+ ```
241
+
242
+ `linearizations()` prints a summary of each available run and returns a list of dictionaries. Each record includes `network`, `solver`, `run`, `path`, `time`, matrix dimensions (`states`, `inputs`, `outputs`), and saved transient point times (`times`). When only transient points were saved, `time` is `None` because there is no separate base result.
243
+
244
+ For programmatic discovery without printing:
245
+
246
+ ```python
247
+ info = file.linearizations(print_output=False)
248
+
249
+ for run in info:
250
+ print(run["solver"], run["time"], run["times"])
251
+ ```
252
+
253
+ ### Read one linearization
254
+
255
+ ```python
256
+ steady = file.linearization(solver="steady_state")
257
+
258
+ print(steady.A)
259
+ print(steady.B)
260
+ print(steady.C)
261
+ print(steady.D)
262
+ ```
263
+
264
+ Read the final/base transient result with:
265
+
266
+ ```python
267
+ final = file.linearization(solver="transient")
268
+ ```
269
+
270
+ Omitting `time` selects the **base result**, not the latest point stored in `points/`. If only saved points are present, specify `time=` or `times=`. If the file contains both steady-state and transient records, select `solver=` explicitly. If only one record is available, it can be inferred.
271
+
272
+ ### Read transient operating points
273
+
274
+ ```python
275
+ point = file.linearization(solver="transient", time=0.1)
276
+
277
+ print(point.time)
278
+ print(point.A.shape)
279
+ print(point.states)
280
+ ```
281
+
282
+ Read several points at once:
283
+
284
+ ```python
285
+ points = file.linearization(
286
+ solver="transient",
287
+ times=[0.1, 0.3],
288
+ )
289
+
290
+ for requested_time, linear in points.items():
291
+ print(requested_time, linear.A, linear.D)
292
+ ```
293
+
294
+ `times=` returns an ordinary dictionary keyed by the **requested** times, even if HDF5 stores `0.3` as `0.30000000000000004`. Times are matched within a narrow floating-point tolerance. If a time does not exist, FullPlot reports the available point times. If multiple points match the same time, it rejects the ambiguous selection.
295
+
296
+ Saved points can have **different numbers of states, inputs, or outputs**. FullPlot returns each result independently; it does not attempt to concatenate incompatible matrices.
297
+
298
+ ### Scope into a run
299
+
300
+ ```python
301
+ run = file.at("/Water_Flow/linearization/transient/runs/base")
302
+
303
+ point = run.linearization(time=0.1)
304
+ final = run.linearization()
305
+ ```
306
+
307
+ A handle scoped directly to a point group can read that point without a time argument:
308
+
309
+ ```python
310
+ point = file.at(
311
+ "/Water_Flow/linearization/transient/runs/base/points/0"
312
+ ).linearization()
313
+ ```
314
+
315
+ If several networks contain matching records, `.linearization()` raises an ambiguity error instead of choosing one arbitrarily. Use `.at()` to select the desired network or run. For non-base model-option runs, scope to the run group or supply its HDF5 run path (such as `run="Pump_Model/polytropic"`). A single unambiguous non-base run can also be selected automatically.
316
+
317
+ ### Data available in `Linearization`
318
+
319
+ | Attribute | Description |
320
+ | --- | --- |
321
+ | `A`, `B`, `C`, `D` | NumPy state-space matrices |
322
+ | `x0`, `xdot0`, `u0`, `y0` | Nominal states, state rates, inputs, and outputs |
323
+ | `states`, `inputs`, `outputs` | Ordered Python string tuples corresponding to matrix rows and columns |
324
+ | `time` | Operating-point time |
325
+ | `diagnostics` | Saved condition numbers and other numerical diagnostics |
326
+ | `model_options` | Saved FullFlow model-option metadata |
327
+ | `network`, `solver`, `run`, `path` | Source identification and HDF5 path |
328
+
329
+ Dimensions are checked on reading:
330
+
331
+ ```math
332
+ A\in\mathbb R^{n_x\times n_x},\quad B\in\mathbb R^{n_x\times n_u},\quad
333
+ C\in\mathbb R^{n_y\times n_x},\quad D\in\mathbb R^{n_y\times n_u}.
334
+ ```
335
+
336
+ FullPlot validates the baseline-vector lengths and requires compatible label counts. HDF5 byte strings are decoded to regular Python strings. Incorrect or missing datasets raise `LinearizationError`.
337
+
338
+ ### Using the matrices
339
+
340
+ Matrix labels determine the meaning of each row and column:
341
+
342
+ ```python
343
+ linear = file.linearization(solver="transient", time=0.1)
344
+
345
+ state_index = linear.states.index("Pipe 1.mass_flow")
346
+ input_index = linear.inputs.index("Pipe 1.cross_sectional_area")
347
+
348
+ sensitivity = linear.B[state_index, input_index]
349
+ print(sensitivity)
350
+ ```
351
+
352
+ For a small deviation from the nominal operating point:
353
+
354
+ ```python
355
+ import numpy as np
356
+
357
+ dx = np.zeros(len(linear.states))
358
+ du = np.zeros(len(linear.inputs))
359
+ du[0] = 1e-6
360
+
361
+ xdot = linear.xdot0 + linear.A @ dx + linear.B @ du
362
+ y = linear.y0 + linear.C @ dx + linear.D @ du
363
+ ```
364
+
365
+ These are **local predictions**, not a substitute for the nonlinear model away from the operating point. FullPlot reads the saved arrays; it does not provide built-in eigenvalue analysis, controller tuning, state-space integration, linearization heat maps, or matrix-coefficient trace extraction.
366
+
367
+ ### FullFlow HDF5 format
368
+
369
+ The reader supports the current FullFlow per-run and per-point layout:
370
+
371
+ ```text
372
+ Network/
373
+ linearization/
374
+ steady_state/
375
+ runs/
376
+ base/
377
+ A, B, C, D
378
+ x0, xdot0, u0, y0
379
+ states, inputs, outputs, time
380
+ diagnostics/, model_options/
381
+ transient/
382
+ runs/
383
+ base/
384
+ A, B, C, D
385
+ x0, xdot0, u0, y0
386
+ states, inputs, outputs, time
387
+ diagnostics/, model_options/
388
+ points/
389
+ 0/
390
+ A, B, C, D, ...
391
+ time
392
+ 1/
393
+ A, B, C, D, ...
394
+ time
395
+ ```
396
+
397
+ Reading a saved linearization is **not** the same as calculating one from a normal simulation-history file. Ordinary result traces alone cannot determine the original system equations or their derivatives. Matrix generation remains FullFlow's responsibility.
398
+
399
+ ### Linearization examples
400
+
401
+ Run the small demo generator, then the two reading examples:
402
+
403
+ ```bash
404
+ python examples/linearization/0generate_linearization_data.py
405
+ python examples/linearization/1inspect_linearization.py
406
+ python examples/linearization/2read_transient_points.py
407
+ ```
408
+
409
+ The demo uses a tiny, self-contained HDF5 file in FullFlow's export format. Replace its filename with your own FullFlow result to inspect real networks. No FullFlow dependency is needed for these reading examples.
410
+
196
411
  ## Core public API
197
412
 
198
413
  Most users only need these names:
@@ -213,6 +428,7 @@ fplt.Trace
213
428
  fplt.TimeAxis
214
429
  fplt.Axis
215
430
  fplt.generate_map
431
+ fplt.Linearization
216
432
  ```
217
433
 
218
434
  The main classes are:
@@ -223,6 +439,7 @@ The main classes are:
223
439
  | `Trace` | Reusable one-dimensional x/y data object. Used for raw data, filtered data, generated limits, commands, and derived traces. |
224
440
  | `TimeAxis` | Shared shiftable and windowable time basis. Several traces can share one axis and follow the same alignment/window. |
225
441
  | `Axis` | Independent variable definition for rectangular-grid map generation. |
442
+ | `Linearization` | Labeled FullFlow state-space matrices and their saved operating point. |
226
443
 
227
444
  Important exceptions are:
228
445
 
@@ -235,6 +452,7 @@ Important exceptions are:
235
452
  | `FullPlotMapError` | Base map-generation exception. |
236
453
  | `MapGenerationError` | Map generation failed during evaluation, resume, or file layout validation. |
237
454
  | `MapOutputError` | The map `evaluate(...)` function returned invalid outputs. |
455
+ | `LinearizationError` | A saved FullFlow linearization is missing, ambiguous, or malformed. |
238
456
 
239
457
  ---
240
458
 
@@ -988,11 +1206,12 @@ The CLI does not create plots. Use the Python API for plotting so scripts can co
988
1206
 
989
1207
  ## Examples
990
1208
 
991
- The repository includes three example folders:
1209
+ The repository includes four example folders:
992
1210
 
993
1211
  ```text
994
1212
  examples/
995
1213
  ├── hdf5_plotting/
1214
+ ├── linearization/
996
1215
  ├── maps/
997
1216
  └── traces/
998
1217
  ```
@@ -1037,6 +1256,9 @@ examples/
1037
1256
  * output discovery,
1038
1257
  * HDF5 layout inspection.
1039
1258
 
1259
+ `examples/linearization/` demonstrates discovering and reading saved FullFlow
1260
+ steady-state and transient state-space models.
1261
+
1040
1262
  ---
1041
1263
 
1042
1264
  ## Themes
@@ -1098,7 +1320,7 @@ This is deliberate. The package is meant to be a simple bridge between HDF5 engi
1098
1320
 
1099
1321
  ---
1100
1322
 
1101
- # Documentation
1323
+ ## Documentation
1102
1324
 
1103
1325
  Full documentation:
1104
1326
 
@@ -159,6 +159,220 @@ settings = run.values("metadata")
159
159
 
160
160
  ---
161
161
 
162
+ ## Reading FullFlow linearizations
163
+
164
+ FullPlot can read **previously calculated** FullFlow steady-state and transient linearization records. The result is a labeled `Linearization` object containing the state-space matrices and the operating point at which they were computed.
165
+
166
+ A continuous-time linear model takes the form
167
+
168
+ ```math
169
+ \Delta\dot{x}=A\Delta x+B\Delta u
170
+ ```
171
+
172
+ ```math
173
+ \Delta y=C\Delta x+D\Delta u
174
+ ```
175
+
176
+ where:
177
+
178
+ - `x`: physical dynamic state vector (for example stored mass or energy),
179
+ - `u`: selected input vector (for example valve area),
180
+ - `y`: selected output vector (for example chamber pressure),
181
+ - `A`: how state changes affect the state derivatives,
182
+ - `B`: how input changes affect the state derivatives,
183
+ - `C`: how state changes affect outputs,
184
+ - `D`: how input changes directly affect outputs.
185
+
186
+ `\Delta` denotes a deviation from the saved operating point. For a non-equilibrium transient point, the model also needs the saved baseline derivative:
187
+
188
+ ```math
189
+ \dot{x}\approx\dot{x}_0+A(x-x_0)+B(u-u_0)
190
+ ```
191
+
192
+ ```math
193
+ y\approx y_0+C(x-x_0)+D(u-u_0)
194
+ ```
195
+
196
+ FullPlot **does not create these matrices**. It reads them from FullFlow's linearization HDF5 output, without needing the original nonlinear network or property-model packages.
197
+
198
+ ### Discover available results
199
+
200
+ ```python
201
+ import fullplot as fplt
202
+
203
+ file = fplt.open("Linearization.h5")
204
+ info = file.linearizations()
205
+ ```
206
+
207
+ `linearizations()` prints a summary of each available run and returns a list of dictionaries. Each record includes `network`, `solver`, `run`, `path`, `time`, matrix dimensions (`states`, `inputs`, `outputs`), and saved transient point times (`times`). When only transient points were saved, `time` is `None` because there is no separate base result.
208
+
209
+ For programmatic discovery without printing:
210
+
211
+ ```python
212
+ info = file.linearizations(print_output=False)
213
+
214
+ for run in info:
215
+ print(run["solver"], run["time"], run["times"])
216
+ ```
217
+
218
+ ### Read one linearization
219
+
220
+ ```python
221
+ steady = file.linearization(solver="steady_state")
222
+
223
+ print(steady.A)
224
+ print(steady.B)
225
+ print(steady.C)
226
+ print(steady.D)
227
+ ```
228
+
229
+ Read the final/base transient result with:
230
+
231
+ ```python
232
+ final = file.linearization(solver="transient")
233
+ ```
234
+
235
+ Omitting `time` selects the **base result**, not the latest point stored in `points/`. If only saved points are present, specify `time=` or `times=`. If the file contains both steady-state and transient records, select `solver=` explicitly. If only one record is available, it can be inferred.
236
+
237
+ ### Read transient operating points
238
+
239
+ ```python
240
+ point = file.linearization(solver="transient", time=0.1)
241
+
242
+ print(point.time)
243
+ print(point.A.shape)
244
+ print(point.states)
245
+ ```
246
+
247
+ Read several points at once:
248
+
249
+ ```python
250
+ points = file.linearization(
251
+ solver="transient",
252
+ times=[0.1, 0.3],
253
+ )
254
+
255
+ for requested_time, linear in points.items():
256
+ print(requested_time, linear.A, linear.D)
257
+ ```
258
+
259
+ `times=` returns an ordinary dictionary keyed by the **requested** times, even if HDF5 stores `0.3` as `0.30000000000000004`. Times are matched within a narrow floating-point tolerance. If a time does not exist, FullPlot reports the available point times. If multiple points match the same time, it rejects the ambiguous selection.
260
+
261
+ Saved points can have **different numbers of states, inputs, or outputs**. FullPlot returns each result independently; it does not attempt to concatenate incompatible matrices.
262
+
263
+ ### Scope into a run
264
+
265
+ ```python
266
+ run = file.at("/Water_Flow/linearization/transient/runs/base")
267
+
268
+ point = run.linearization(time=0.1)
269
+ final = run.linearization()
270
+ ```
271
+
272
+ A handle scoped directly to a point group can read that point without a time argument:
273
+
274
+ ```python
275
+ point = file.at(
276
+ "/Water_Flow/linearization/transient/runs/base/points/0"
277
+ ).linearization()
278
+ ```
279
+
280
+ If several networks contain matching records, `.linearization()` raises an ambiguity error instead of choosing one arbitrarily. Use `.at()` to select the desired network or run. For non-base model-option runs, scope to the run group or supply its HDF5 run path (such as `run="Pump_Model/polytropic"`). A single unambiguous non-base run can also be selected automatically.
281
+
282
+ ### Data available in `Linearization`
283
+
284
+ | Attribute | Description |
285
+ | --- | --- |
286
+ | `A`, `B`, `C`, `D` | NumPy state-space matrices |
287
+ | `x0`, `xdot0`, `u0`, `y0` | Nominal states, state rates, inputs, and outputs |
288
+ | `states`, `inputs`, `outputs` | Ordered Python string tuples corresponding to matrix rows and columns |
289
+ | `time` | Operating-point time |
290
+ | `diagnostics` | Saved condition numbers and other numerical diagnostics |
291
+ | `model_options` | Saved FullFlow model-option metadata |
292
+ | `network`, `solver`, `run`, `path` | Source identification and HDF5 path |
293
+
294
+ Dimensions are checked on reading:
295
+
296
+ ```math
297
+ A\in\mathbb R^{n_x\times n_x},\quad B\in\mathbb R^{n_x\times n_u},\quad
298
+ C\in\mathbb R^{n_y\times n_x},\quad D\in\mathbb R^{n_y\times n_u}.
299
+ ```
300
+
301
+ FullPlot validates the baseline-vector lengths and requires compatible label counts. HDF5 byte strings are decoded to regular Python strings. Incorrect or missing datasets raise `LinearizationError`.
302
+
303
+ ### Using the matrices
304
+
305
+ Matrix labels determine the meaning of each row and column:
306
+
307
+ ```python
308
+ linear = file.linearization(solver="transient", time=0.1)
309
+
310
+ state_index = linear.states.index("Pipe 1.mass_flow")
311
+ input_index = linear.inputs.index("Pipe 1.cross_sectional_area")
312
+
313
+ sensitivity = linear.B[state_index, input_index]
314
+ print(sensitivity)
315
+ ```
316
+
317
+ For a small deviation from the nominal operating point:
318
+
319
+ ```python
320
+ import numpy as np
321
+
322
+ dx = np.zeros(len(linear.states))
323
+ du = np.zeros(len(linear.inputs))
324
+ du[0] = 1e-6
325
+
326
+ xdot = linear.xdot0 + linear.A @ dx + linear.B @ du
327
+ y = linear.y0 + linear.C @ dx + linear.D @ du
328
+ ```
329
+
330
+ These are **local predictions**, not a substitute for the nonlinear model away from the operating point. FullPlot reads the saved arrays; it does not provide built-in eigenvalue analysis, controller tuning, state-space integration, linearization heat maps, or matrix-coefficient trace extraction.
331
+
332
+ ### FullFlow HDF5 format
333
+
334
+ The reader supports the current FullFlow per-run and per-point layout:
335
+
336
+ ```text
337
+ Network/
338
+ linearization/
339
+ steady_state/
340
+ runs/
341
+ base/
342
+ A, B, C, D
343
+ x0, xdot0, u0, y0
344
+ states, inputs, outputs, time
345
+ diagnostics/, model_options/
346
+ transient/
347
+ runs/
348
+ base/
349
+ A, B, C, D
350
+ x0, xdot0, u0, y0
351
+ states, inputs, outputs, time
352
+ diagnostics/, model_options/
353
+ points/
354
+ 0/
355
+ A, B, C, D, ...
356
+ time
357
+ 1/
358
+ A, B, C, D, ...
359
+ time
360
+ ```
361
+
362
+ Reading a saved linearization is **not** the same as calculating one from a normal simulation-history file. Ordinary result traces alone cannot determine the original system equations or their derivatives. Matrix generation remains FullFlow's responsibility.
363
+
364
+ ### Linearization examples
365
+
366
+ Run the small demo generator, then the two reading examples:
367
+
368
+ ```bash
369
+ python examples/linearization/0generate_linearization_data.py
370
+ python examples/linearization/1inspect_linearization.py
371
+ python examples/linearization/2read_transient_points.py
372
+ ```
373
+
374
+ The demo uses a tiny, self-contained HDF5 file in FullFlow's export format. Replace its filename with your own FullFlow result to inspect real networks. No FullFlow dependency is needed for these reading examples.
375
+
162
376
  ## Core public API
163
377
 
164
378
  Most users only need these names:
@@ -179,6 +393,7 @@ fplt.Trace
179
393
  fplt.TimeAxis
180
394
  fplt.Axis
181
395
  fplt.generate_map
396
+ fplt.Linearization
182
397
  ```
183
398
 
184
399
  The main classes are:
@@ -189,6 +404,7 @@ The main classes are:
189
404
  | `Trace` | Reusable one-dimensional x/y data object. Used for raw data, filtered data, generated limits, commands, and derived traces. |
190
405
  | `TimeAxis` | Shared shiftable and windowable time basis. Several traces can share one axis and follow the same alignment/window. |
191
406
  | `Axis` | Independent variable definition for rectangular-grid map generation. |
407
+ | `Linearization` | Labeled FullFlow state-space matrices and their saved operating point. |
192
408
 
193
409
  Important exceptions are:
194
410
 
@@ -201,6 +417,7 @@ Important exceptions are:
201
417
  | `FullPlotMapError` | Base map-generation exception. |
202
418
  | `MapGenerationError` | Map generation failed during evaluation, resume, or file layout validation. |
203
419
  | `MapOutputError` | The map `evaluate(...)` function returned invalid outputs. |
420
+ | `LinearizationError` | A saved FullFlow linearization is missing, ambiguous, or malformed. |
204
421
 
205
422
  ---
206
423
 
@@ -954,11 +1171,12 @@ The CLI does not create plots. Use the Python API for plotting so scripts can co
954
1171
 
955
1172
  ## Examples
956
1173
 
957
- The repository includes three example folders:
1174
+ The repository includes four example folders:
958
1175
 
959
1176
  ```text
960
1177
  examples/
961
1178
  ├── hdf5_plotting/
1179
+ ├── linearization/
962
1180
  ├── maps/
963
1181
  └── traces/
964
1182
  ```
@@ -1003,6 +1221,9 @@ examples/
1003
1221
  * output discovery,
1004
1222
  * HDF5 layout inspection.
1005
1223
 
1224
+ `examples/linearization/` demonstrates discovering and reading saved FullFlow
1225
+ steady-state and transient state-space models.
1226
+
1006
1227
  ---
1007
1228
 
1008
1229
  ## Themes
@@ -1064,7 +1285,7 @@ This is deliberate. The package is meant to be a simple bridge between HDF5 engi
1064
1285
 
1065
1286
  ---
1066
1287
 
1067
- # Documentation
1288
+ ## Documentation
1068
1289
 
1069
1290
  Full documentation:
1070
1291
 
@@ -0,0 +1,46 @@
1
+ """Create a small FullFlow-style HDF5 file for the linearization reading examples.
2
+
3
+ The matrices below are illustrative. No FullFlow installation is required.
4
+ """
5
+
6
+ from pathlib import Path
7
+
8
+ import h5py
9
+ import numpy as np
10
+
11
+
12
+ filename = Path(__file__).resolve().parent / "linearization_demo.h5"
13
+
14
+
15
+ def write_result(group, time, gain):
16
+ group.attrs["solver_type"] = "transient"
17
+ group.create_dataset("A", data=[[-0.25 * gain]])
18
+ group.create_dataset("B", data=[[0.5 * gain]])
19
+ group.create_dataset("C", data=[[1.0]])
20
+ group.create_dataset("D", data=[[0.0]])
21
+ group.create_dataset("x0", data=[10.0 - time])
22
+ group.create_dataset("xdot0", data=[-0.2 * gain])
23
+ group.create_dataset("u0", data=[0.5])
24
+ group.create_dataset("y0", data=[10.0 - time])
25
+ labels = h5py.string_dtype("utf-8")
26
+ group.create_dataset("states", data=["Tank:mass"], dtype=labels)
27
+ group.create_dataset("inputs", data=["Valve.area"], dtype=labels)
28
+ group.create_dataset("outputs", data=["Tank.pressure"], dtype=labels)
29
+ group.create_dataset("time", data=time)
30
+ group.create_group("model_options")
31
+ diagnostics = group.create_group("diagnostics")
32
+ diagnostics.attrs["sensitivity_condition_number"] = 1.0
33
+
34
+
35
+ with h5py.File(filename, "w") as h5:
36
+ run = h5.require_group("Tank/linearization/transient/runs/base")
37
+ write_result(run, 1.0, 1.0)
38
+ points = run.create_group("points")
39
+ write_result(points.create_group("0"), 0.1, 0.8)
40
+ write_result(points.create_group("1"), 0.3, 0.9)
41
+
42
+ steady = h5.require_group("Tank/linearization/steady_state/runs/base")
43
+ write_result(steady, 1.0, 1.0)
44
+ steady.attrs["solver_type"] = "steady_state"
45
+
46
+ print("Created", filename)
@@ -0,0 +1,29 @@
1
+ """Read the matrices and labels of a FullFlow linearization HDF5 file.
2
+
3
+ Run 0generate_linearization_data.py first, or replace the filename with a
4
+ linearization file generated by FullFlow.
5
+ """
6
+
7
+ from pathlib import Path
8
+
9
+ import fullplot as fplt
10
+
11
+
12
+ example_dir = Path(__file__).resolve().parent
13
+ filename = example_dir / "linearization_demo.h5"
14
+
15
+ file = fplt.open(filename)
16
+ file.linearizations()
17
+
18
+ steady = file.linearization(solver="steady_state")
19
+
20
+ print("\nSteady-state linearization")
21
+ print("Time:", steady.time)
22
+ print("States:", steady.states)
23
+ print("Inputs:", steady.inputs)
24
+ print("Outputs:", steady.outputs)
25
+ print("A:\n", steady.A)
26
+ print("B:\n", steady.B)
27
+ print("C:\n", steady.C)
28
+ print("D:\n", steady.D)
29
+ print("Diagnostics:", steady.diagnostics)
@@ -0,0 +1,32 @@
1
+ """Read multiple transient operating points from a FullFlow HDF5 file.
2
+
3
+ Run 0generate_linearization_data.py first, or replace the filename with a
4
+ transient linearization file generated by FullFlow.
5
+ """
6
+
7
+ from pathlib import Path
8
+
9
+ import fullplot as fplt
10
+
11
+
12
+ example_dir = Path(__file__).resolve().parent
13
+ filename = example_dir / "linearization_demo.h5"
14
+
15
+ file = fplt.open(filename)
16
+
17
+ final = file.linearization(solver="transient")
18
+ print("Final/base operating point:", final.time)
19
+ print("Final A:\n", final.A)
20
+
21
+ points = file.linearization(solver="transient", times=[0.1, 0.3])
22
+
23
+ for time, linear in points.items():
24
+ print(f"\nTime: {time:g} s")
25
+ print("State labels:", linear.states)
26
+ print("Input labels:", linear.inputs)
27
+ print("A:\n", linear.A)
28
+ print("B:\n", linear.B)
29
+
30
+ run = file.at("/Tank/linearization/transient/runs/base")
31
+ point = run.linearization(time=0.1)
32
+ print("\nScoped result at:", point.time)