microstructure-tpw 0.3.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.
Files changed (119) hide show
  1. microstructure_tpw-0.3.0.dist-info/METADATA +234 -0
  2. microstructure_tpw-0.3.0.dist-info/RECORD +119 -0
  3. microstructure_tpw-0.3.0.dist-info/WHEEL +5 -0
  4. microstructure_tpw-0.3.0.dist-info/entry_points.txt +7 -0
  5. microstructure_tpw-0.3.0.dist-info/licenses/LICENSE +674 -0
  6. microstructure_tpw-0.3.0.dist-info/top_level.txt +1 -0
  7. odas_tpw/__init__.py +18 -0
  8. odas_tpw/chi/__init__.py +48 -0
  9. odas_tpw/chi/batchelor.py +189 -0
  10. odas_tpw/chi/chi.py +857 -0
  11. odas_tpw/chi/fp07.py +649 -0
  12. odas_tpw/chi/l2_chi.py +153 -0
  13. odas_tpw/chi/l3_chi.py +388 -0
  14. odas_tpw/chi/l4_chi.py +391 -0
  15. odas_tpw/config_base.py +423 -0
  16. odas_tpw/perturb/__init__.py +18 -0
  17. odas_tpw/perturb/adcp.py +274 -0
  18. odas_tpw/perturb/atomic_io.py +47 -0
  19. odas_tpw/perturb/binning.py +761 -0
  20. odas_tpw/perturb/cli.py +591 -0
  21. odas_tpw/perturb/combo.py +346 -0
  22. odas_tpw/perturb/config.py +1046 -0
  23. odas_tpw/perturb/ctd.py +424 -0
  24. odas_tpw/perturb/diag/__init__.py +12 -0
  25. odas_tpw/perturb/diag/_common.py +140 -0
  26. odas_tpw/perturb/diag/chi.py +42 -0
  27. odas_tpw/perturb/diag/cli.py +67 -0
  28. odas_tpw/perturb/diag/data.py +662 -0
  29. odas_tpw/perturb/diag/epsilon.py +45 -0
  30. odas_tpw/perturb/diag/inspector.py +342 -0
  31. odas_tpw/perturb/diag/mixing.py +94 -0
  32. odas_tpw/perturb/diag/render.py +543 -0
  33. odas_tpw/perturb/discover.py +40 -0
  34. odas_tpw/perturb/fp07_cal.py +354 -0
  35. odas_tpw/perturb/gps.py +458 -0
  36. odas_tpw/perturb/hotel.py +572 -0
  37. odas_tpw/perturb/logging_setup.py +181 -0
  38. odas_tpw/perturb/merge.py +320 -0
  39. odas_tpw/perturb/mk_sections.py +310 -0
  40. odas_tpw/perturb/netcdf_schema.py +520 -0
  41. odas_tpw/perturb/pipeline.py +2654 -0
  42. odas_tpw/perturb/plot/__init__.py +15 -0
  43. odas_tpw/perturb/plot/cli.py +107 -0
  44. odas_tpw/perturb/plot/diagnostics.py +195 -0
  45. odas_tpw/perturb/plot/eps_chi.py +524 -0
  46. odas_tpw/perturb/plot/figure.py +679 -0
  47. odas_tpw/perturb/plot/gamma_scaling.py +1097 -0
  48. odas_tpw/perturb/plot/grid.py +112 -0
  49. odas_tpw/perturb/plot/layout.py +361 -0
  50. odas_tpw/perturb/plot/overview.py +547 -0
  51. odas_tpw/perturb/plot/presets/eps-chi.yaml +16 -0
  52. odas_tpw/perturb/plot/presets/profiles.yaml +32 -0
  53. odas_tpw/perturb/plot/presets/scalar.yaml +21 -0
  54. odas_tpw/perturb/plot/profiles.py +652 -0
  55. odas_tpw/perturb/plot/scalar.py +391 -0
  56. odas_tpw/perturb/plot/sections.py +506 -0
  57. odas_tpw/perturb/plot/xaxis.py +396 -0
  58. odas_tpw/perturb/qc_gate.py +177 -0
  59. odas_tpw/perturb/qc_rules.py +373 -0
  60. odas_tpw/perturb/resolve.py +306 -0
  61. odas_tpw/perturb/seawater.py +76 -0
  62. odas_tpw/perturb/speed.py +14 -0
  63. odas_tpw/perturb/trim.py +313 -0
  64. odas_tpw/processing/__init__.py +62 -0
  65. odas_tpw/processing/bottom.py +215 -0
  66. odas_tpw/processing/chi_combine.py +300 -0
  67. odas_tpw/processing/ct_align.py +172 -0
  68. odas_tpw/processing/epsilon_combine.py +210 -0
  69. odas_tpw/processing/mixing.py +630 -0
  70. odas_tpw/processing/thorpe.py +458 -0
  71. odas_tpw/processing/top_trim.py +274 -0
  72. odas_tpw/pyturb/__init__.py +3 -0
  73. odas_tpw/pyturb/__main__.py +5 -0
  74. odas_tpw/pyturb/_compat.py +204 -0
  75. odas_tpw/pyturb/_profind.py +124 -0
  76. odas_tpw/pyturb/bin.py +113 -0
  77. odas_tpw/pyturb/cli.py +175 -0
  78. odas_tpw/pyturb/eps.py +353 -0
  79. odas_tpw/pyturb/merge.py +101 -0
  80. odas_tpw/pyturb/p2nc.py +76 -0
  81. odas_tpw/rsi/__init__.py +31 -0
  82. odas_tpw/rsi/adapter.py +314 -0
  83. odas_tpw/rsi/bench.py +904 -0
  84. odas_tpw/rsi/binning.py +193 -0
  85. odas_tpw/rsi/channels.py +376 -0
  86. odas_tpw/rsi/chi_io.py +980 -0
  87. odas_tpw/rsi/cli.py +1411 -0
  88. odas_tpw/rsi/combine.py +120 -0
  89. odas_tpw/rsi/config.py +126 -0
  90. odas_tpw/rsi/config_patch.py +790 -0
  91. odas_tpw/rsi/convert.py +651 -0
  92. odas_tpw/rsi/deconvolve.py +119 -0
  93. odas_tpw/rsi/diss_look.py +343 -0
  94. odas_tpw/rsi/dissipation.py +669 -0
  95. odas_tpw/rsi/helpers.py +527 -0
  96. odas_tpw/rsi/mixing_look.py +509 -0
  97. odas_tpw/rsi/p_file.py +771 -0
  98. odas_tpw/rsi/pipeline.py +924 -0
  99. odas_tpw/rsi/profile.py +624 -0
  100. odas_tpw/rsi/quick_look.py +758 -0
  101. odas_tpw/rsi/sensor_inventory.py +684 -0
  102. odas_tpw/rsi/shear_noise.py +128 -0
  103. odas_tpw/rsi/speed.py +221 -0
  104. odas_tpw/rsi/vehicle.py +74 -0
  105. odas_tpw/rsi/viewer_base.py +1071 -0
  106. odas_tpw/rsi/window.py +459 -0
  107. odas_tpw/scor160/__init__.py +36 -0
  108. odas_tpw/scor160/cli.py +242 -0
  109. odas_tpw/scor160/compare.py +484 -0
  110. odas_tpw/scor160/despike.py +227 -0
  111. odas_tpw/scor160/goodman.py +288 -0
  112. odas_tpw/scor160/io.py +690 -0
  113. odas_tpw/scor160/l2.py +311 -0
  114. odas_tpw/scor160/l3.py +279 -0
  115. odas_tpw/scor160/l4.py +850 -0
  116. odas_tpw/scor160/nasmyth.py +208 -0
  117. odas_tpw/scor160/ocean.py +287 -0
  118. odas_tpw/scor160/profile.py +231 -0
  119. odas_tpw/scor160/spectral.py +482 -0
odas_tpw/__init__.py ADDED
@@ -0,0 +1,18 @@
1
+ # odas-tpw: Ocean microstructure turbulence processing (ODAS heritage)
2
+ #
3
+ # Subpackages:
4
+ # rsi — Rockland Scientific instrument I/O (.p files, NetCDF, profiles)
5
+ # chi — Chi (thermal variance dissipation) calculation
6
+ # scor160 — ATOMIX shear-probe benchmark processing (L1-L4)
7
+ # processing — Instrument-agnostic algorithms (top_trim, bottom, ct_align, etc.)
8
+ # perturb — Full processing pipeline (trim, merge, calibrate, compute, bin)
9
+
10
+ from importlib.metadata import PackageNotFoundError
11
+ from importlib.metadata import version as _pkg_version
12
+
13
+ try:
14
+ __version__ = _pkg_version("microstructure-tpw")
15
+ except PackageNotFoundError: # pragma: no cover - running from an uninstalled source tree
16
+ __version__ = "0.0.0+unknown"
17
+
18
+ __all__ = ["__version__"]
@@ -0,0 +1,48 @@
1
+ # Chi (thermal variance dissipation) calculations
2
+ #
3
+ # Computes chi from temperature microstructure spectra using
4
+ # Batchelor/Kraichnan spectral fitting (Method 2) or from known
5
+ # epsilon via Dillon & Caldwell (Method 1).
6
+
7
+ from odas_tpw.chi.batchelor import (
8
+ KAPPA_T,
9
+ Q_BATCHELOR,
10
+ Q_KRAICHNAN,
11
+ batchelor_grad,
12
+ batchelor_kB,
13
+ kraichnan_grad,
14
+ )
15
+ from odas_tpw.chi.fp07 import (
16
+ default_tau_model,
17
+ fp07_double_pole,
18
+ fp07_tau,
19
+ fp07_tau_batch,
20
+ fp07_transfer,
21
+ fp07_transfer_batch,
22
+ )
23
+ from odas_tpw.chi.l2_chi import L2ChiData, L2ChiParams, process_l2_chi
24
+ from odas_tpw.chi.l3_chi import L3ChiData, process_l3_chi
25
+ from odas_tpw.chi.l4_chi import L4ChiData, process_l4_chi_epsilon, process_l4_chi_fit
26
+
27
+ __all__ = [
28
+ "KAPPA_T",
29
+ "Q_BATCHELOR",
30
+ "Q_KRAICHNAN",
31
+ "L2ChiData",
32
+ "L2ChiParams",
33
+ "L3ChiData",
34
+ "L4ChiData",
35
+ "batchelor_grad",
36
+ "batchelor_kB",
37
+ "default_tau_model",
38
+ "fp07_double_pole",
39
+ "fp07_tau",
40
+ "fp07_tau_batch",
41
+ "fp07_transfer",
42
+ "fp07_transfer_batch",
43
+ "kraichnan_grad",
44
+ "process_l2_chi",
45
+ "process_l3_chi",
46
+ "process_l4_chi_epsilon",
47
+ "process_l4_chi_fit",
48
+ ]
@@ -0,0 +1,189 @@
1
+ # Mar-2026, Claude and Pat Welch, pat@mousebrains.com
2
+ """Batchelor and Kraichnan temperature gradient spectra.
3
+
4
+ References
5
+ ----------
6
+ Dillon, T.M. and D.R. Caldwell, 1980: The Batchelor spectrum and dissipation
7
+ in the upper ocean. J. Geophys. Res., 85, 1910-1916.
8
+ Bogucki, D., J.A. Domaradzki, and P.K. Yeung, 1997: Direct numerical
9
+ simulations of passive scalars with Pr > 1 advected by turbulent flow.
10
+ J. Fluid Mech., 343, 111-130.
11
+ Oakey, N.S., 1982: Determination of the rate of dissipation of turbulent
12
+ energy. J. Phys. Oceanogr., 12, 256-271.
13
+ """
14
+
15
+ import numpy as np
16
+ import numpy.typing as npt
17
+ from scipy.special import erfc
18
+
19
+ # ---------------------------------------------------------------------------
20
+ # Constants
21
+ # ---------------------------------------------------------------------------
22
+
23
+ # Thermal diffusivity of seawater [m^2/s] at a ~15 degC reference — the BACKWARD-
24
+ # COMPATIBLE FALLBACK only. The production chi pipeline now threads a per-window,
25
+ # temperature-dependent kappa_T(T,S,P) (Sharqawy-2010 conductivity + gsw rho/cp,
26
+ # see scor160/ocean.py:kappa_T) from the L3 stage through chi.py/l4_chi.py. That
27
+ # T-dependence spans ~1.39e-7 at -1 degC to ~1.51e-7 at 32 degC; since
28
+ # chi = 6*kappa_T*I and epsilon_T = (2*pi*kB)^4*nu*kappa_T^2, using it removed a
29
+ # chi bias that ran from ~-1% (cold) to ~+8% (warm) against this fixed value.
30
+ # This constant remains the default for the chi.py functions' `kappa_T=` argument
31
+ # so a caller that does not supply a per-window value still runs.
32
+ # See docs/chi_mathematics.md Section 11.
33
+ KAPPA_T = 1.4e-7
34
+ Q_BATCHELOR = 3.7 # Batchelor universal constant (Oakey 1982)
35
+ Q_KRAICHNAN = 5.26 # Kraichnan universal constant (Bogucki et al. 1997)
36
+ _SQ6Q_KRAICHNAN = np.sqrt(6 * Q_KRAICHNAN) # Precomputed for kraichnan_grad
37
+
38
+
39
+ # ---------------------------------------------------------------------------
40
+ # Batchelor wavenumber
41
+ # ---------------------------------------------------------------------------
42
+
43
+
44
+ def batchelor_kB(
45
+ epsilon: float | np.ndarray, nu: float, kappa_T: float = KAPPA_T
46
+ ) -> float | np.ndarray:
47
+ """Batchelor wavenumber [cpm].
48
+
49
+ kB = (1/(2*pi)) * (epsilon / (nu * kappa_T^2))^(1/4)
50
+
51
+ Parameters
52
+ ----------
53
+ epsilon : float or array
54
+ TKE dissipation rate [W/kg].
55
+ nu : float
56
+ Kinematic viscosity [m^2/s].
57
+ kappa_T : float
58
+ Thermal diffusivity [m^2/s].
59
+
60
+ Returns
61
+ -------
62
+ kB : float or array
63
+ Batchelor wavenumber [cpm].
64
+ """
65
+ return (1 / (2 * np.pi)) * (epsilon / (nu * kappa_T**2)) ** 0.25
66
+
67
+
68
+ # ---------------------------------------------------------------------------
69
+ # Batchelor spectrum (Dillon & Caldwell 1980)
70
+ # ---------------------------------------------------------------------------
71
+
72
+
73
+ def batchelor_nondim(alpha: npt.ArrayLike) -> np.ndarray:
74
+ """Non-dimensional Batchelor gradient spectrum f_B(alpha).
75
+
76
+ f(alpha) = alpha * [exp(-alpha^2/2) - alpha * sqrt(pi/2) * erfc(alpha/sqrt(2))]
77
+
78
+ Dillon & Caldwell 1980, eq. 3. Integral over [0, inf) = 1/3.
79
+
80
+ Parameters
81
+ ----------
82
+ alpha : array_like
83
+ Non-dimensional wavenumber: alpha = sqrt(2*q) * k / kB.
84
+
85
+ Returns
86
+ -------
87
+ f : ndarray
88
+ """
89
+ alpha = np.asarray(alpha, dtype=np.float64)
90
+ return alpha * (np.exp(-(alpha**2) / 2) - alpha * np.sqrt(np.pi / 2) * erfc(alpha / np.sqrt(2)))
91
+
92
+
93
+ def batchelor_grad(
94
+ k: npt.ArrayLike,
95
+ kB: float | np.ndarray,
96
+ chi: float,
97
+ kappa_T: float = KAPPA_T,
98
+ q: float = Q_BATCHELOR,
99
+ ) -> np.ndarray:
100
+ """Batchelor temperature gradient spectrum [(K/m)^2 / cpm].
101
+
102
+ S(k) = sqrt(q/2) * chi / (kB * kappa_T) * f(alpha)
103
+ where alpha = sqrt(2*q) * k / kB
104
+
105
+ Dillon & Caldwell 1980, eq. 1. k and kB in cpm.
106
+ Integrates to chi / (6 * kappa_T).
107
+
108
+ Parameters
109
+ ----------
110
+ k : array_like
111
+ Wavenumber [cpm].
112
+ kB : float
113
+ Batchelor wavenumber [cpm].
114
+ chi : float
115
+ Thermal variance dissipation rate [K^2/s].
116
+ kappa_T : float
117
+ Thermal diffusivity [m^2/s].
118
+ q : float
119
+ Universal constant.
120
+
121
+ Returns
122
+ -------
123
+ S : ndarray
124
+ Gradient spectrum [(K/m)^2 / cpm].
125
+ """
126
+ k = np.asarray(k, dtype=np.float64)
127
+ # Mirror kraichnan_grad: a degenerate kB (0 / NaN) otherwise yields
128
+ # NaN-with-warnings; sanitize to 0 so the spectrum stays finite (#16).
129
+ with np.errstate(divide="ignore", invalid="ignore"):
130
+ alpha = np.sqrt(2 * q) * k / kB
131
+ f = batchelor_nondim(alpha)
132
+ S = np.sqrt(q / 2) * chi / (kB * kappa_T) * f
133
+ return np.where(np.isfinite(S), S, 0.0)
134
+
135
+
136
+ # ---------------------------------------------------------------------------
137
+ # Kraichnan spectrum (Bogucki et al. 1997)
138
+ # ---------------------------------------------------------------------------
139
+
140
+
141
+ def kraichnan_grad(
142
+ k: npt.ArrayLike,
143
+ kB: float | np.ndarray,
144
+ chi: float,
145
+ kappa_T: float = KAPPA_T,
146
+ q: float = Q_KRAICHNAN,
147
+ ) -> np.ndarray:
148
+ """Kraichnan temperature gradient spectrum [(K/m)^2 / cpm].
149
+
150
+ S(k) = chi*q / (kappa_T*kB^2) * k * exp(-sqrt(6q)*y)
151
+ where y = k / kB.
152
+
153
+ This is the one-dimensional (along-path) gradient spectrum obtained by
154
+ applying the isotropic transform G1(k1) = k1^2 * int_{k1}^inf E(k)/k dk
155
+ to the three-dimensional Kraichnan scalar spectrum of Bogucki et al.
156
+ 1997, eq. 11. The transform evaluates in closed form to the simple
157
+ exponential above (the same form as Peterson & Fer 2014, eq. 8).
158
+ Integrates to chi / (6 * kappa_T); peaks at k = kB / sqrt(6q).
159
+
160
+ Key difference from Batchelor: exponential rolloff instead of Gaussian,
161
+ which fits DNS and observed high-Prandtl-number spectra better
162
+ (Bogucki et al. 1997).
163
+
164
+ Parameters
165
+ ----------
166
+ k : array_like
167
+ Wavenumber [cpm].
168
+ kB : float
169
+ Batchelor wavenumber [cpm].
170
+ chi : float
171
+ Thermal variance dissipation rate [K^2/s].
172
+ kappa_T : float
173
+ Thermal diffusivity [m^2/s].
174
+ q : float
175
+ Universal constant (default: 5.26).
176
+
177
+ Returns
178
+ -------
179
+ S : ndarray
180
+ Gradient spectrum [(K/m)^2 / cpm].
181
+ """
182
+ k = np.asarray(k, dtype=np.float64)
183
+ y = k / kB
184
+ sq6q = _SQ6Q_KRAICHNAN if q == Q_KRAICHNAN else np.sqrt(6 * q)
185
+ sq6q_y = sq6q * y
186
+ with np.errstate(divide="ignore", invalid="ignore"):
187
+ S = chi * q / (kappa_T * kB**2) * k * np.exp(-sq6q_y)
188
+ S = np.where(np.isfinite(S), S, 0.0)
189
+ return S