PyTracerLab 0.2.0__py3-none-any.whl

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.
@@ -0,0 +1,626 @@
1
+ """Base classes for model units."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import ABC, abstractmethod
6
+ from dataclasses import dataclass
7
+ from typing import Dict
8
+
9
+ import numpy as np
10
+ from scipy import integrate
11
+
12
+
13
+ class Unit(ABC):
14
+ """Abstract base class for a model unit.
15
+
16
+ Concrete units represent hydrological transport schemata and must expose
17
+ and accept their **local** parameter values via a mapping. Units are
18
+ intentionally unaware of optimization bounds; those live in the Model's
19
+ parameter registry.
20
+
21
+ Notes
22
+ -----
23
+ - Implementations must keep local parameter names *stable* over time so that
24
+ the Model's registry stays consistent.
25
+ - Names should be short (e.g., ``"mtt"``, ``"eta"``).
26
+ """
27
+
28
+ @abstractmethod
29
+ def param_values(self) -> Dict[str, float]:
30
+ """Return current local parameter values.
31
+
32
+ Returns
33
+ -------
34
+ Dict[str, float]
35
+ Mapping from local parameter name to value.
36
+ """
37
+ raise NotImplementedError
38
+
39
+ @abstractmethod
40
+ def set_param_values(self, values: Dict[str, float]) -> None:
41
+ """Set one or more local parameter values.
42
+
43
+ Parameters
44
+ ----------
45
+ values : Dict[str, float]
46
+ Mapping from local parameter name to new value. Keys not present
47
+ are ignored.
48
+ """
49
+ raise NotImplementedError
50
+
51
+ def get_block(
52
+ self, h: np.ndarray, tau: np.ndarray, dt: float, lambda_: float, prod: bool = False
53
+ ) -> np.ndarray:
54
+ """Get 1-dt block response."""
55
+ # area = float(h.sum() * dt)
56
+ area = integrate.trapezoid(h, dx=dt)
57
+ if not np.isfinite(area) or area <= 0:
58
+ raise ValueError(f"Impulse response has non-positive/invalid area: {area}")
59
+ h /= area
60
+
61
+ # decay / production
62
+ if prod:
63
+ h *= 1 - np.exp(-lambda_ * tau)
64
+ else:
65
+ h *= np.exp(-lambda_ * tau)
66
+
67
+ step = np.cumsum(h) * dt
68
+ step = integrate.cumulative_trapezoid(h, dx=dt, initial=0)
69
+ block = np.append(step[0], np.subtract(step[1:], step[:-1]))
70
+
71
+ return block
72
+
73
+ def normalize_response(self, h: np.ndarray, dt: float) -> np.ndarray:
74
+ """Normalize impulse response to unit area for conservation of mass."""
75
+ area = float(h.sum() * dt)
76
+ if not np.isfinite(area) or area <= 0:
77
+ raise ValueError(f"Impulse response has non-positive/invalid area: {area}")
78
+ h /= area
79
+
80
+ return h
81
+
82
+ @abstractmethod
83
+ def get_impulse_response(self, tau: np.ndarray, dt: float, lambda_: float) -> np.ndarray:
84
+ """Evaluate the unit's impulse response on a time grid.
85
+
86
+ Parameters
87
+ ----------
88
+ tau : ndarray
89
+ Non-negative time axis (same spacing as simulation time grid).
90
+ dt : float
91
+ Time step size of the discretization.
92
+ lambda_ : float
93
+ Decay constant (1 / time units of ``tau``).
94
+
95
+ Returns
96
+ -------
97
+ ndarray
98
+ Impulse response sampled at ``tau``.
99
+ """
100
+ raise NotImplementedError
101
+
102
+
103
+ @dataclass
104
+ class EPMUnit(Unit):
105
+ """Exponential Piston-Flow Model (EPM) unit.
106
+
107
+ Parameters
108
+ ----------
109
+ mtt : float
110
+ Mean travel time.
111
+ eta : float
112
+ Ratio of total volume to the exponential reservoir (>= 1). ``eta=1``
113
+ reduces to a pure exponential model; ``eta>1`` adds a piston component.
114
+ PREFIX : str
115
+ Prefix for local parameter names. Helper for GUI.
116
+ PARAMS : List[Dict[str, Any]]
117
+ List of (default) parameter definitions. Helper for GUI.
118
+
119
+ Note: The parameter key for the mean travel time (``mtt``) is used in the
120
+ GUI explicitly. The GUI assumes the parameter is given in years and
121
+ internally converts it.
122
+ """
123
+
124
+ mtt: float
125
+ eta: float
126
+ PREFIX = "epm"
127
+ # The parameter keys are used explicitly in the GUI! Changing them will lead
128
+ # to the GUI not displaying parameter units properly.
129
+ PARAMS = [
130
+ {"key": "mtt", "label": "Mean Transit Time", "default": 10.0, "bounds": (0.0, 10000.0)},
131
+ {"key": "eta", "label": "Eta", "default": 1.1, "bounds": (1.0, 2.0)},
132
+ ]
133
+
134
+ def param_values(self) -> Dict[str, float]:
135
+ """Get parameter values.
136
+
137
+ Returns
138
+ -------
139
+ Dict[str, float]
140
+ Mapping from local parameter name to value.
141
+ """
142
+ return {"mtt": float(self.mtt), "eta": float(self.eta)}
143
+
144
+ def set_param_values(self, values: Dict[str, float]) -> None:
145
+ """Set one or more local parameter values.
146
+
147
+ Parameters
148
+ ----------
149
+ values : Dict[str, float]
150
+ Mapping from local parameter name to new value. Keys not present
151
+ are ignored.
152
+ """
153
+ if "mtt" in values:
154
+ self.mtt = float(values["mtt"])
155
+ if "eta" in values:
156
+ self.eta = float(values["eta"])
157
+
158
+ def get_impulse_response(
159
+ self, tau: np.ndarray, dt: float, lambda_: float, prod: bool = False
160
+ ) -> np.ndarray:
161
+ """EPM impulse response with decay.
162
+
163
+ The continuous-time EPM response (without decay) is
164
+ ``h(τ) = (η/mtt) * exp(-η τ / mtt + η - 1)`` for
165
+ ``τ >= mtt*(1 - 1/η)`` and ``0`` otherwise. We also apply
166
+ an exponential decay term ``exp(-λ τ)``.
167
+
168
+ Parameters
169
+ ----------
170
+ tau : ndarray
171
+ Non-negative time axis (same spacing as simulation time grid).
172
+ dt : float
173
+ Time step size of the discretization.
174
+ lambda_ : float
175
+ Decay constant (1 / time units of ``tau``).
176
+ prod : bool, optional
177
+ If True, calculate production response (used to simulate 3He
178
+ production from 3H decay).
179
+
180
+ Returns
181
+ -------
182
+ ndarray
183
+ Impulse response evaluated at ``tau``.
184
+ """
185
+ # check for edge cases
186
+ if self.eta <= 1.0 or self.mtt <= 0.0:
187
+ return np.zeros_like(tau)
188
+
189
+ # Note: a non-zero response at h[0] corresponds to a response at the
190
+ # same time step as the forcing. This is usually not what we want, so
191
+ # we need to make sure the first time bin is 0.
192
+
193
+ # We don't need to shift the age grid here because we (almost surely)
194
+ # avoid having non-zero response at h[0] anyways for the EPM.
195
+
196
+ # base EPM shape
197
+ h_prelim = (self.eta / self.mtt) * np.exp(-self.eta * tau / self.mtt + self.eta - 1.0)
198
+ cutoff = self.mtt * (1.0 - 1.0 / self.eta)
199
+ h = np.where(tau <= cutoff, 0.0, h_prelim)
200
+
201
+ # get response for constant-input block of length dt
202
+ h = self.get_block(h, tau, dt, lambda_, prod)
203
+
204
+ return h
205
+
206
+
207
+ @dataclass
208
+ class ExEPMUnit(Unit):
209
+ """Explicit xponential Piston-Flow Model (EPM) unit.
210
+ This model is essentially the same as the EPMUnit, but the EPM ratio
211
+ (total volume / exponential volume or total area / area receiving
212
+ recharge) is defined via two parameters instead of one aggregated
213
+ parameter. Those two parameters are directly related and can never be
214
+ estimated simultaneously.
215
+
216
+ Parameters
217
+ ----------
218
+ mtt : float
219
+ Mean travel time.
220
+ exp_part: float
221
+ Area receiving recharge or exponential volume of the system.
222
+ piston_part: float
223
+ Area not receiving recharge or piston-flow volume of the system.
224
+ PREFIX : str
225
+ Prefix for local parameter names. Helper for GUI.
226
+ PARAMS : List[Dict[str, Any]]
227
+ List of (default) parameter definitions. Helper for GUI.
228
+
229
+ Note: The parameter key for the mean travel time (``mtt``) is used in the
230
+ GUI explicitly. The GUI assumes the parameter is given in years and
231
+ internally converts it.
232
+ """
233
+
234
+ mtt: float
235
+ exp_part: float
236
+ piston_part: float
237
+ PREFIX = "epm"
238
+ # The parameter keys are used explicitly in the GUI! Changing them will lead
239
+ # to the GUI not displaying parameter units properly.
240
+ PARAMS = [
241
+ {"key": "mtt", "label": "Mean Transit Time", "default": 10.0, "bounds": (0.0, 10000.0)},
242
+ {"key": "exp_part", "label": "Exponential Part", "default": 0.5, "bounds": (0.0, 100.0)},
243
+ {"key": "piston_part", "label": "Piston Part", "default": 1.0, "bounds": (0.0, 100.0)},
244
+ ]
245
+
246
+ def param_values(self) -> Dict[str, float]:
247
+ """Get parameter values.
248
+
249
+ Returns
250
+ -------
251
+ Dict[str, float]
252
+ Mapping from local parameter name to value.
253
+ """
254
+ return {
255
+ "mtt": float(self.mtt),
256
+ "exp_part": float(self.exp_part),
257
+ "piston_part": float(self.piston_part),
258
+ }
259
+
260
+ def set_param_values(self, values: Dict[str, float]) -> None:
261
+ """Set one or more local parameter values.
262
+
263
+ Parameters
264
+ ----------
265
+ values : Dict[str, float]
266
+ Mapping from local parameter name to new value. Keys not present
267
+ are ignored.
268
+ """
269
+ if "mtt" in values:
270
+ self.mtt = float(values["mtt"])
271
+ if "exp_part" in values:
272
+ self.exp_part = float(values["exp_part"])
273
+ if "piston_part" in values:
274
+ self.piston_part = float(values["piston_part"])
275
+
276
+ def get_impulse_response(
277
+ self, tau: np.ndarray, dt: float, lambda_: float, prod: bool = False
278
+ ) -> np.ndarray:
279
+ """ExEPM impulse response with decay.
280
+
281
+ The continuous-time EPM response (without decay) is
282
+ ``h(τ) = (η/mtt) * exp(-η τ / mtt + η - 1)`` for
283
+ ``τ >= mtt*(1 - 1/η)`` and ``0`` otherwise. We also apply
284
+ an exponential decay term ``exp(-λ τ)``.
285
+
286
+ Parameters
287
+ ----------
288
+ tau : ndarray
289
+ Non-negative time axis (same spacing as simulation time grid).
290
+ dt : float
291
+ Time step size of the discretization.
292
+ lambda_ : float
293
+ Decay constant (1 / time units of ``tau``).
294
+ prod : bool, optional
295
+ If True, calculate production response (used to simulate 3He
296
+ production from 3H decay).
297
+
298
+ Returns
299
+ -------
300
+ ndarray
301
+ Impulse response evaluated at ``tau``.
302
+ """
303
+ # calculate eta
304
+ eta = (self.piston_part / self.exp_part) + 1
305
+
306
+ # check for edge cases
307
+ if eta <= 1.0 or self.mtt <= 0.0:
308
+ return np.zeros_like(tau)
309
+
310
+ # Note: a non-zero response at h[0] corresponds to a response at the
311
+ # same time step as the forcing. This is usually not what we want, so
312
+ # we need to make sure the first time bin is 0.
313
+
314
+ # We don't need to shift the age grid here because we (almost surely)
315
+ # avoid having non-zero response at h[0] anyways for the EPM.
316
+
317
+ # base EPM shape
318
+ h_prelim = (eta / self.mtt) * np.exp(-eta * tau / self.mtt + eta - 1.0)
319
+ cutoff = self.mtt * (1.0 - 1.0 / eta)
320
+ h = np.where(tau < cutoff, 0.0, h_prelim)
321
+
322
+ # get response for constant-input block of length dt
323
+ h = self.get_block(h, tau, dt, lambda_, prod)
324
+
325
+ return h
326
+
327
+
328
+ @dataclass
329
+ class DMUnit(Unit):
330
+ """Dispersion Model (DM) unit.
331
+
332
+ Parameters
333
+ ----------
334
+ mtt : float
335
+ Mean travel time.
336
+ DP : float
337
+ Dispersion parameter. Represents the inverse of the Peclet number.
338
+ Also represents the ratio of the dispersion coefficient to the
339
+ velocity and outlet / sampling position
340
+ PREFIX : str
341
+ Prefix for local parameter names. Helper for GUI.
342
+ PARAMS : List[Dict[str, Any]]
343
+ List of (default) parameter definitions. Helper for GUI.
344
+
345
+ Note: The parameter key for the mean travel time (``mtt``) is used in the
346
+ GUI explicitly. The GUI assumes the parameter is given in years and
347
+ internally converts it.
348
+ """
349
+
350
+ mtt: float
351
+ DP: float
352
+ PREFIX = "dm"
353
+ # The parameter keys are used explicitly in the GUI! Changing them will lead
354
+ # to the GUI not displaying parameter units properly.
355
+ PARAMS = [
356
+ {"key": "mtt", "label": "Mean Transit Time", "default": 10.0, "bounds": (1.0, 10000.0)},
357
+ {"key": "DP", "label": "Dispersion Param.", "default": 1.0, "bounds": (0.0001, 10.0)},
358
+ ]
359
+
360
+ def param_values(self) -> Dict[str, float]:
361
+ """Get parameter values.
362
+
363
+ Returns
364
+ -------
365
+ Dict[str, float]
366
+ Mapping from local parameter name to value.
367
+ """
368
+ return {"mtt": float(self.mtt), "DP": float(self.DP)}
369
+
370
+ def set_param_values(self, values: Dict[str, float]) -> None:
371
+ """Set one or more local parameter values.
372
+
373
+ Parameters
374
+ ----------
375
+ values : Dict[str, float]
376
+ Mapping from local parameter name to new value. Keys not present
377
+ are ignored.
378
+ """
379
+ if "mtt" in values:
380
+ self.mtt = float(values["mtt"])
381
+ if "DP" in values:
382
+ self.DP = float(values["DP"])
383
+
384
+ def get_impulse_response(
385
+ self, tau: np.ndarray, dt: float, lambda_: float, prod: bool = False
386
+ ) -> np.ndarray:
387
+ """DM impulse response with decay.
388
+
389
+ The continuous-time DM response (without decay) is
390
+ ``h(τ) = (1/mtt) * 1 / (sqrt(K)) * exp((1 - τ / mtt)^2 / K)`` with
391
+ ``K = 4 pi DP (τ / mtt)``. We also apply an exponential decay
392
+ term ``exp(-λ τ)``.
393
+
394
+ Parameters
395
+ ----------
396
+ tau : ndarray
397
+ Non-negative time axis (same spacing as simulation time grid).
398
+ dt : float
399
+ Time step size of the discretization.
400
+ lambda_ : float
401
+ Decay constant (1 / time units of ``tau``).
402
+ prod : bool, optional
403
+ If True, calculate production response (used to simulate 3He
404
+ production from 3H decay).
405
+
406
+ Returns
407
+ -------
408
+ ndarray
409
+ Impulse response evaluated at ``tau``.
410
+ """
411
+ # Check for edge cases
412
+ if self.DP <= 0.0 or self.mtt <= 0.0:
413
+ return np.zeros_like(tau)
414
+
415
+ # Note: a non-zero response at h[0] corresponds to a response at the
416
+ # same time step as the forcing. This is usually not what we want, so
417
+ # we need to make sure the first time bin is 0.
418
+
419
+ # The transfer function breaks down as τ tends towards 0
420
+ # We therefore prepare a result array for h and fill it up, starting
421
+ # with the non-zero values
422
+ h = np.zeros_like(tau)
423
+
424
+ # Pre-compute terms
425
+ buffer = 1
426
+ a = tau[buffer:] * np.sqrt(4 * np.pi * self.DP * tau[buffer:] / self.mtt)
427
+ b = -((1 - tau[buffer:] / self.mtt) ** 2.0 / (4 * self.DP * tau[buffer:] / self.mtt))
428
+
429
+ h[buffer:] = 1 / a * np.exp(b)
430
+
431
+ # get response for constant-input block of length dt
432
+ h = self.get_block(h, tau, dt, lambda_, prod)
433
+
434
+ return h
435
+
436
+
437
+ @dataclass
438
+ class EMUnit(Unit):
439
+ """Exponential Model (EM) unit.
440
+
441
+ Parameters
442
+ ----------
443
+ mtt : float
444
+ Mean travel time.
445
+ PREFIX : str
446
+ Prefix for local parameter names. Helper for GUI.
447
+ PARAMS : List[Dict[str, Any]]
448
+ List of (default) parameter definitions. Helper for GUI.
449
+
450
+ Note: The parameter key for the mean travel time (``mtt``) is used in the
451
+ GUI explicitly. The GUI assumes the parameter is given in years and
452
+ internally converts it.
453
+ """
454
+
455
+ mtt: float
456
+ PREFIX = "em"
457
+ # The parameter keys are used explicitly in the GUI! Changing them will lead
458
+ # to the GUI not displaying parameter units properly.
459
+ PARAMS = [
460
+ {"key": "mtt", "label": "Mean Transit Time", "default": 10.0, "bounds": (0.0, 10000.0)},
461
+ ]
462
+
463
+ def param_values(self) -> Dict[str, float]:
464
+ """Get parameter values.
465
+
466
+ Returns
467
+ -------
468
+ Dict[str, float]
469
+ Mapping from local parameter name to value.
470
+ """
471
+ return {"mtt": float(self.mtt)}
472
+
473
+ def set_param_values(self, values: Dict[str, float]) -> None:
474
+ """Set one or more local parameter values.
475
+
476
+ Parameters
477
+ ----------
478
+ values : Dict[str, float]
479
+ Mapping from local parameter name to new value. Keys not present
480
+ are ignored.
481
+ """
482
+ if "mtt" in values:
483
+ self.mtt = float(values["mtt"])
484
+
485
+ def get_impulse_response(
486
+ self, tau: np.ndarray, dt: float, lambda_: float, prod: bool = False
487
+ ) -> np.ndarray:
488
+ """EM impulse response with decay.
489
+
490
+ The continuous-time EPM response (without decay) is
491
+ ``h(τ) = (1/mtt) * exp(-τ / mtt)``. We also apply an exponential
492
+ decay term ``exp(-λ τ)``.
493
+
494
+ Parameters
495
+ ----------
496
+ tau : ndarray
497
+ Non-negative time axis (same spacing as simulation time grid).
498
+ dt : float
499
+ Time step size of the discretization.
500
+ lambda_ : float
501
+ Decay constant (1 / time units of ``tau``).
502
+ prod : bool, optional
503
+ If True, calculate production response (used to simulate 3He
504
+ production from 3H decay).
505
+
506
+ Returns
507
+ -------
508
+ ndarray
509
+ Impulse response evaluated at ``tau``.
510
+ """
511
+ # check for edge cases
512
+ if self.mtt <= 0.0:
513
+ return np.zeros_like(tau)
514
+
515
+ # Note: a non-zero response at h[0] corresponds to a response at the
516
+ # same time step as the forcing. This is usually not what we want, so
517
+ # we need to make sure the first time bin is 0.
518
+
519
+ # Base EM shape
520
+ h = np.zeros_like(tau)
521
+ h = (1 / self.mtt) * np.exp(-tau / self.mtt)
522
+
523
+ # get response for constant-input block of length dt
524
+ h = self.get_block(h, tau, dt, lambda_, prod)
525
+
526
+ return h
527
+
528
+
529
+ @dataclass
530
+ class PMUnit(Unit):
531
+ """Piston-Flow Model (discrete delta at the mean travel time) with decay.
532
+
533
+ Parameters
534
+ ----------
535
+ mtt : float
536
+ Mean travel time where all mass is transported as a plug flow.
537
+ PREFIX : str
538
+ Prefix for local parameter names. Helper for GUI.
539
+ PARAMS : List[Dict[str, Any]]
540
+ List of (default) parameter definitions. Helper for GUI.
541
+
542
+ Note: The parameter key for the mean travel time (``mtt``) is used in the
543
+ GUI explicitly. The GUI assumes the parameter is given in years and
544
+ internally converts it.
545
+ """
546
+
547
+ mtt: float
548
+ PREFIX = "pm"
549
+ # The parameter keys are used explicitly in the GUI! Changing them will lead
550
+ # to the GUI not displaying parameter units properly.
551
+ PARAMS = [
552
+ {"key": "mtt", "label": "Mean Transit Time", "default": 10.0, "bounds": (0.0, 10000.0)},
553
+ ]
554
+
555
+ def param_values(self) -> Dict[str, float]:
556
+ """Get parameter values.
557
+
558
+ Returns
559
+ -------
560
+ Dict[str, float]
561
+ Mapping from local parameter name to value.
562
+ """
563
+ return {"mtt": float(self.mtt)}
564
+
565
+ def set_param_values(self, values: Dict[str, float]) -> None:
566
+ """Set local parameter value.
567
+
568
+ Parameters
569
+ ----------
570
+ values : Dict[str, float]
571
+ Mapping from local parameter name to new value. Keys not present
572
+ are ignored.
573
+ """
574
+ if "mtt" in values:
575
+ self.mtt = float(values["mtt"])
576
+
577
+ def get_impulse_response(
578
+ self, tau: np.ndarray, dt: float, lambda_: float, prod: bool = False
579
+ ) -> np.ndarray:
580
+ """Discrete delta response on the grid with exponential decay.
581
+
582
+ The delta is represented by setting the bin at ``round(mtt/dt)`` to
583
+ ``1/dt`` to preserve unit mass in the discrete sum.
584
+
585
+ Parameters
586
+ ----------
587
+ tau : ndarray
588
+ Non-negative time axis (same spacing as simulation time grid).
589
+ dt : float
590
+ Time step size of the discretization.
591
+ lambda_ : float
592
+ Decay constant (1 / time units of ``tau``).
593
+ prod : bool, optional
594
+ If True, calculate production response (used to simulate 3He
595
+ production from 3H decay).
596
+
597
+ Returns
598
+ -------
599
+ ndarray
600
+ Impulse response evaluated at ``tau``.
601
+ """
602
+ # Check for edge cases
603
+ if self.mtt <= 0.0:
604
+ return np.zeros_like(tau)
605
+
606
+ # Note: a non-zero response at h[0] corresponds to a response at the
607
+ # same time step as the forcing. This is usually not what we want, so
608
+ # we need to make sure the first time bin is 0.
609
+
610
+ # We don't need to shift the age grid here because we (almost surely)
611
+ # avoid having non-zero response at h[0] anyways for the PM.
612
+
613
+ h = np.zeros_like(tau)
614
+ idx = max(1, int(round(self.mtt / dt)))
615
+
616
+ # Mass is already preserved: before radioactive decay, we set a
617
+ # single time bin to 1/dt. Multiplying by dt and dividing by the
618
+ # sum just gives us 1/dt again.
619
+ if 0 <= idx < len(tau):
620
+ h[idx] = 1.0 / dt
621
+ # radioactive/first-order decay/production
622
+ if prod:
623
+ h[idx] *= 1 - np.exp(-lambda_ * self.mtt)
624
+ else:
625
+ h[idx] *= np.exp(-lambda_ * self.mtt)
626
+ return h
@@ -0,0 +1,37 @@
1
+ Metadata-Version: 2.4
2
+ Name: PyTracerLab
3
+ Version: 0.2.0
4
+ Summary: Lumped parameter groundwater age simulations with a simple user interface - in Python
5
+ Author: Max G. Rudolph
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/iGW-TU-Dresden/PyTracerLab
8
+ Project-URL: Documentation, https://iGW-TU-Dresden.github.io/PyTracerLab/
9
+ Classifier: Programming Language :: Python :: 3 :: Only
10
+ Classifier: Programming Language :: Python :: 3.9
11
+ Classifier: Topic :: Scientific/Engineering :: Hydrology
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Intended Audience :: Education
14
+ Classifier: Intended Audience :: End Users/Desktop
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: Microsoft :: Windows :: Windows 10
17
+ Classifier: Typing :: Typed
18
+ Requires-Python: >=3.9
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: numpy>=1.24
22
+ Requires-Dist: matplotlib>=3.7
23
+ Requires-Dist: scipy>=1.10
24
+ Requires-Dist: PyQt5>=5.15
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest; extra == "dev"
27
+ Requires-Dist: pre-commit; extra == "dev"
28
+ Requires-Dist: black; extra == "dev"
29
+ Requires-Dist: ruff; extra == "dev"
30
+ Requires-Dist: pre-commit-hooks; extra == "dev"
31
+ Requires-Dist: mkdocs-material; extra == "dev"
32
+ Requires-Dist: mkdocstrings[python]; extra == "dev"
33
+ Requires-Dist: sphinx; extra == "dev"
34
+ Requires-Dist: furo; extra == "dev"
35
+ Requires-Dist: myst-parser; extra == "dev"
36
+ Requires-Dist: sphinx-autodoc-typehints; extra == "dev"
37
+ Dynamic: license-file
@@ -0,0 +1,26 @@
1
+ PyTracerLab/__init__.py,sha256=wqJZyPjhg3CkrN1Sr7-ALpdhCa5r4x9efZNbNg5zaqw,223
2
+ PyTracerLab/__main__.py,sha256=PfT1B9EcIvj6YbEkvqiIFoVngTdhFzWm-l4qFPUbfE8,173
3
+ PyTracerLab/gui/__init__.py,sha256=mdt-HHOLaQzO6rCgedujnRA-2Tbic3C7mwtZIt1ZD9Y,208
4
+ PyTracerLab/gui/app.py,sha256=6o016docAXtuvTbr9a2IdO4qlOq_Bp2lfkmdQ6E1TYA,315
5
+ PyTracerLab/gui/controller.py,sha256=8ILvwNblAMZce7uiiy7kbsxmPNva46L2n5-9eDWUJ1M,16691
6
+ PyTracerLab/gui/database.py,sha256=2WGXLtgB3U_qnwZqDCNb_PCCx1UApTmWaDq1NK1K4fQ,431
7
+ PyTracerLab/gui/main_window.py,sha256=Aro-bTPBqXdzgBKRhbDDdA9CjJO5-F2PpabxJNFDyWE,2834
8
+ PyTracerLab/gui/state.py,sha256=d_s9tR3gliKfNjLlGVX3wiyeb17wOpTRZTEMVXNftA0,4664
9
+ PyTracerLab/gui/tabs/file_input.py,sha256=AwV8GOHms2JwmeI75c4s9WYLI0qR5QJprvzMZFXVJoc,16488
10
+ PyTracerLab/gui/tabs/model_design.py,sha256=sl7fX4vtFYpzT8N2fOcofR-BlHa4z-tFg3a2bbvAWOE,11654
11
+ PyTracerLab/gui/tabs/parameters.py,sha256=KFSGEfj2ySup-NKIk9mK6tFFYH4ndfpgjQTVzmRRcQk,4647
12
+ PyTracerLab/gui/tabs/simulation.py,sha256=_eAAV0YHFSXAuqI-T2EsnZUz27S2-vkZMibH9meqia8,10974
13
+ PyTracerLab/gui/tabs/solver_params.py,sha256=VS4-EHK171CM5SIgrmdwiKr9rQzlbjOgOQu8sG8kWlM,26966
14
+ PyTracerLab/gui/tabs/tracer_tracer.py,sha256=BhZGsXJFMaHF492_HU-c0Uq2WYQHJdZdRe0JQc65ow4,11974
15
+ PyTracerLab/gui/tabs/widgets.py,sha256=xx9Jtkx59C_RQuXGVOWMi8_2hlPQHoZpgNxlLE7SACU,4019
16
+ PyTracerLab/model/__init__.py,sha256=Ook-7H4M-2q6uHlrYrTp68-4Hox2XiBvXQp_teCXGmI,1426
17
+ PyTracerLab/model/model.py,sha256=c3TY65vzTcdabCNZmNTaGAwP8zxVLBsy3ktVdXrBpJw,27743
18
+ PyTracerLab/model/registry.py,sha256=xUnnktqY4LFGLg8mwNS2bwZ79IhVwjObiNAutRS9tpw,828
19
+ PyTracerLab/model/solver.py,sha256=xKx8CsjbWpGrBq2Fnj2RdpNqzJYyi7xJOihz4qHdw-4,50819
20
+ PyTracerLab/model/units.py,sha256=UpGbbOFsoP1ooizKPFmSpahnnF6vGlq4kL5XoybfEk0,20949
21
+ pytracerlab-0.2.0.dist-info/licenses/LICENSE,sha256=yEpJ3xwftS_OOYaa_VkfGzZwP2Qxp7XWfI9zHwWXYjI,1080
22
+ pytracerlab-0.2.0.dist-info/METADATA,sha256=eY9VzFkMVKyLm0aaCtSK1__-ObtbhcF2tYx-dN-mK5E,1494
23
+ pytracerlab-0.2.0.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
24
+ pytracerlab-0.2.0.dist-info/entry_points.txt,sha256=RLc_5LOvpI6yYLmtr2WPwepke-dXdPZISFR9L40nzC8,115
25
+ pytracerlab-0.2.0.dist-info/top_level.txt,sha256=49vsBEZgWxSXH-u_dF2MX8qJPreibUp49FCCGEwqvF8,12
26
+ pytracerlab-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,5 @@
1
+ [console_scripts]
2
+ PyTracerLab = PyTracerLab.gui.app:main
3
+
4
+ [gui_scripts]
5
+ PyTracerLab-gui = PyTracerLab.gui.app:main