jerlov 0.7.2__tar.gz → 0.8.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.
- {jerlov-0.7.2/jerlov.egg-info → jerlov-0.8.0}/PKG-INFO +12 -5
- {jerlov-0.7.2 → jerlov-0.8.0}/README.md +3 -1
- {jerlov-0.7.2 → jerlov-0.8.0}/jerlov/__init__.py +1 -1
- {jerlov-0.7.2 → jerlov-0.8.0}/jerlov/shortwave.py +22 -3
- {jerlov-0.7.2 → jerlov-0.8.0}/jerlov/water.py +58 -17
- {jerlov-0.7.2 → jerlov-0.8.0/jerlov.egg-info}/PKG-INFO +12 -5
- {jerlov-0.7.2 → jerlov-0.8.0}/jerlov.egg-info/requires.txt +0 -3
- {jerlov-0.7.2 → jerlov-0.8.0}/pyproject.toml +9 -3
- {jerlov-0.7.2 → jerlov-0.8.0}/tests/test_api.py +9 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/tests/test_depth.py +29 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/tests/test_packaging.py +47 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/tests/test_shortwave.py +27 -3
- {jerlov-0.7.2 → jerlov-0.8.0}/LICENSE +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/NOTICE +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/jerlov/_data.py +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/jerlov/backscattering.py +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/jerlov/colour.py +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/jerlov/data/__init__.py +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/jerlov/data/austin1986_kd.csv +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/jerlov/data/austin1986_model.csv +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/jerlov/data/boss2001_chi.csv +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/jerlov/data/cie1931_2deg_cmf.csv +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/jerlov/data/cie_d65.csv +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/jerlov/data/jerlov1968_kd.csv +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/jerlov/data/jerlov1968_total_irradiance.csv +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/jerlov/data/jerlov1976_kd.csv +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/jerlov/data/paulson1977_shortwave.csv +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/jerlov/data/petzold1972_stations.csv +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/jerlov/data/petzold1972_vsf.csv +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/jerlov/data/smart2007_b_from_c.csv +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/jerlov/data/solonenko2015_iop.csv +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/jerlov/data/williamson2022_aw.csv +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/jerlov/data/williamson2022_iop.csv +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/jerlov/data/williamson2022_measured.csv +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/jerlov/data/williamson2023_depth.csv +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/jerlov/petzold.py +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/jerlov/py.typed +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/jerlov/scene.py +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/jerlov/sources.py +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/jerlov.egg-info/SOURCES.txt +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/jerlov.egg-info/dependency_links.txt +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/jerlov.egg-info/top_level.txt +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/setup.cfg +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/tests/test_backscattering.py +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/tests/test_colour.py +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/tests/test_petzold.py +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/tests/test_quoted_figures.py +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/tests/test_reproduces_papers.py +0 -0
- {jerlov-0.7.2 → jerlov-0.8.0}/tests/test_scene.py +0 -0
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jerlov
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: Inherent optical properties of Jerlov water types, with provenance
|
|
5
5
|
Author: T. Ishibashi
|
|
6
6
|
License-Expression: Apache-2.0
|
|
7
7
|
Keywords: ocean optics,Jerlov water types,inherent optical properties
|
|
8
|
-
Classifier: Development Status ::
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
9
|
Classifier: Intended Audience :: Science/Research
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
10
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
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
|
|
11
17
|
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
18
|
+
Classifier: Typing :: Typed
|
|
12
19
|
Requires-Python: >=3.10
|
|
13
20
|
Description-Content-Type: text/markdown
|
|
14
21
|
License-File: LICENSE
|
|
15
22
|
License-File: NOTICE
|
|
16
23
|
Requires-Dist: numpy>=1.22
|
|
17
|
-
Provides-Extra: plot
|
|
18
|
-
Requires-Dist: matplotlib; extra == "plot"
|
|
19
24
|
Provides-Extra: test
|
|
20
25
|
Requires-Dist: pytest; extra == "test"
|
|
21
26
|
Provides-Extra: build
|
|
@@ -72,7 +77,8 @@ paper's own constant, so constants are attached to sources.
|
|
|
72
77
|
|
|
73
78
|
## What this package will not do
|
|
74
79
|
|
|
75
|
-
- **Extrapolate.** Asking for a wavelength outside the data raises.
|
|
80
|
+
- **Extrapolate.** Asking for a wavelength outside the data raises. Values
|
|
81
|
+
that a source itself extrapolated are returned, but marked, and they warn.
|
|
76
82
|
- **Fill a gap.** Where a published value is wrong and could not be
|
|
77
83
|
recovered, the value is NaN and stays NaN through interpolation.
|
|
78
84
|
- **Supply bb.** The backscattering coefficient is not determined by the
|
|
@@ -260,6 +266,7 @@ coastal water clears as you go down.
|
|
|
260
266
|
jerlov.water_type_at_depth("I", 60.0) # 'IB'
|
|
261
267
|
jerlov.water_type_at_depth("3C", 45.0) # 'II'
|
|
262
268
|
jerlov.water_type_at_depth("9C", 15.0) # None: the paper declined to say
|
|
269
|
+
jerlov.water_type_at_depth("I", [0, 30, 60]) # array(['I', 'IA', 'IB'], dtype=object)
|
|
263
270
|
```
|
|
264
271
|
|
|
265
272
|
`None` means fewer than ten measurement campaigns supported a declaration, so
|
|
@@ -47,7 +47,8 @@ paper's own constant, so constants are attached to sources.
|
|
|
47
47
|
|
|
48
48
|
## What this package will not do
|
|
49
49
|
|
|
50
|
-
- **Extrapolate.** Asking for a wavelength outside the data raises.
|
|
50
|
+
- **Extrapolate.** Asking for a wavelength outside the data raises. Values
|
|
51
|
+
that a source itself extrapolated are returned, but marked, and they warn.
|
|
51
52
|
- **Fill a gap.** Where a published value is wrong and could not be
|
|
52
53
|
recovered, the value is NaN and stays NaN through interpolation.
|
|
53
54
|
- **Supply bb.** The backscattering coefficient is not determined by the
|
|
@@ -235,6 +236,7 @@ coastal water clears as you go down.
|
|
|
235
236
|
jerlov.water_type_at_depth("I", 60.0) # 'IB'
|
|
236
237
|
jerlov.water_type_at_depth("3C", 45.0) # 'II'
|
|
237
238
|
jerlov.water_type_at_depth("9C", 15.0) # None: the paper declined to say
|
|
239
|
+
jerlov.water_type_at_depth("I", [0, 30, 60]) # array(['I', 'IA', 'IB'], dtype=object)
|
|
238
240
|
```
|
|
239
241
|
|
|
240
242
|
`None` means fewer than ten measurement campaigns supported a declaration, so
|
|
@@ -15,12 +15,13 @@ them from Jerlov (1968) Table XXI and reproduces R for every row.
|
|
|
15
15
|
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
|
+
import warnings
|
|
18
19
|
from dataclasses import dataclass
|
|
19
20
|
|
|
20
21
|
import numpy as np
|
|
21
22
|
|
|
22
23
|
from . import _data
|
|
23
|
-
from .water import _like_input, _require_number
|
|
24
|
+
from .water import ProvenanceWarning, _like_input, _require_number
|
|
24
25
|
|
|
25
26
|
CITATION = (
|
|
26
27
|
"Paulson, C. A. and Simpson, J. J. (1977), 'Irradiance measurements in "
|
|
@@ -42,15 +43,33 @@ class ShortwaveParameters:
|
|
|
42
43
|
zeta2_m: float
|
|
43
44
|
"""Attenuation length of the slow term, metres. Blue-green light."""
|
|
44
45
|
fit_depth_m: float | None
|
|
45
|
-
"""Depth
|
|
46
|
+
"""Depth the row was fitted down to: 100 m, or 50 m for ``I_upper50``.
|
|
47
|
+
The paper's text says so; the table does not. None for the rows that are
|
|
48
|
+
not Jerlov types."""
|
|
46
49
|
note: str
|
|
47
50
|
|
|
48
51
|
def fraction_at(self, depth_m):
|
|
49
|
-
"""``I(z)/I(0)`` at one or more depths.
|
|
52
|
+
"""``I(z)/I(0)`` at one or more depths.
|
|
53
|
+
|
|
54
|
+
Below :attr:`fit_depth_m` the two exponentials are extrapolated
|
|
55
|
+
beyond the data they were fitted to, and a
|
|
56
|
+
:class:`~jerlov.ProvenanceWarning` says so. Jerlov's own Table XXI
|
|
57
|
+
goes deeper for the clearer types; see
|
|
58
|
+
:func:`jerlov1968_solar_fraction`.
|
|
59
|
+
"""
|
|
50
60
|
z = np.atleast_1d(np.asarray(depth_m, dtype=float))
|
|
51
61
|
_require_number(z, "depth_m")
|
|
52
62
|
if np.any(z < 0):
|
|
53
63
|
raise ValueError("depth_m cannot be negative")
|
|
64
|
+
if self.fit_depth_m is not None and np.any(z > self.fit_depth_m):
|
|
65
|
+
warnings.warn(
|
|
66
|
+
f"{self.key}: Paulson & Simpson (1977) fitted these "
|
|
67
|
+
f"parameters to the upper {self.fit_depth_m:g} m; at "
|
|
68
|
+
f"{float(np.max(z)):g} m the two exponentials are "
|
|
69
|
+
"extrapolated. See DATA.md section 14.",
|
|
70
|
+
ProvenanceWarning,
|
|
71
|
+
stacklevel=_data.caller_stacklevel(),
|
|
72
|
+
)
|
|
54
73
|
out = (self.R * np.exp(-z / self.zeta1_m)
|
|
55
74
|
+ (1 - self.R) * np.exp(-z / self.zeta2_m))
|
|
56
75
|
return _like_input(out, depth_m)
|
|
@@ -148,6 +148,14 @@ class Water:
|
|
|
148
148
|
self.source = source
|
|
149
149
|
self._flags = dict(flags or {})
|
|
150
150
|
for key, statuses in self._flags.items():
|
|
151
|
+
if key not in self._series:
|
|
152
|
+
# Ignoring it would drop the caller's doubt without a word,
|
|
153
|
+
# which is what flags exist to prevent; a misspelt key such
|
|
154
|
+
# as "kd" for "Kd" would never warn.
|
|
155
|
+
raise ValueError(
|
|
156
|
+
f"flags given for {key!r}, which this water does not "
|
|
157
|
+
f"carry (it has {', '.join(sorted(self._series))})"
|
|
158
|
+
)
|
|
151
159
|
if len(statuses) != self.wavelengths.size:
|
|
152
160
|
raise ValueError(
|
|
153
161
|
f"flags[{key!r}] has {len(statuses)} entries; it needs one "
|
|
@@ -467,7 +475,7 @@ def water(water_type: str, source: str = "williamson2022") -> Water:
|
|
|
467
475
|
)
|
|
468
476
|
|
|
469
477
|
|
|
470
|
-
def water_type_at_depth(surface_water_type: str, depth_m
|
|
478
|
+
def water_type_at_depth(surface_water_type: str, depth_m):
|
|
471
479
|
"""The Jerlov type that typically applies at ``depth_m``.
|
|
472
480
|
|
|
473
481
|
The classification is defined on the top 10 m, but clarity changes with
|
|
@@ -480,6 +488,11 @@ def water_type_at_depth(surface_water_type: str, depth_m: float) -> str | None:
|
|
|
480
488
|
wherever fewer than ten campaigns supported one. Coastal types run out
|
|
481
489
|
quickly: 3C is declared only to 70 m, and 9C not at all below 10 m.
|
|
482
490
|
|
|
491
|
+
``depth_m`` may be a single depth, which gives a string or ``None``, or
|
|
492
|
+
an array of depths, which gives an object array of the same shape::
|
|
493
|
+
|
|
494
|
+
jerlov.water_type_at_depth("I", [0, 30, 60]) # ['I', 'IA', 'IB']
|
|
495
|
+
|
|
483
496
|
This is a lookup, not a correction applied on your behalf. To use it::
|
|
484
497
|
|
|
485
498
|
deeper = jerlov.water_type_at_depth("I", 60.0) # "IB"
|
|
@@ -491,12 +504,12 @@ def water_type_at_depth(surface_water_type: str, depth_m: float) -> str | None:
|
|
|
491
504
|
any particular place or season. The paper says so explicitly, and gives
|
|
492
505
|
per-cell cruise and month counts for anyone who needs to judge that.
|
|
493
506
|
"""
|
|
494
|
-
|
|
495
|
-
if not np.isfinite(
|
|
507
|
+
depths = np.asarray(depth_m, dtype=float)
|
|
508
|
+
if not np.all(np.isfinite(depths)):
|
|
496
509
|
# NaN fails every comparison below, so it would otherwise fall
|
|
497
510
|
# through to "no statement" and look like a deliberate answer.
|
|
498
|
-
raise ValueError(f"depth_m must be
|
|
499
|
-
if
|
|
511
|
+
raise ValueError(f"depth_m must be finite numbers, not {depth_m!r}")
|
|
512
|
+
if np.any(depths < 0):
|
|
500
513
|
raise ValueError("depth_m cannot be negative")
|
|
501
514
|
rows = _data._rows("williamson2023_depth.csv")
|
|
502
515
|
known = {r["surface_water_type"] for r in rows}
|
|
@@ -505,20 +518,31 @@ def water_type_at_depth(surface_water_type: str, depth_m: float) -> str | None:
|
|
|
505
518
|
f"unknown water type {surface_water_type!r} "
|
|
506
519
|
f"(known: {', '.join(sorted(known))})"
|
|
507
520
|
)
|
|
508
|
-
own = [r
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
521
|
+
own = [(float(r["depth_min_m"]), float(r["depth_max_m"]),
|
|
522
|
+
r["water_type"] or None)
|
|
523
|
+
for r in rows if r["surface_water_type"] == surface_water_type]
|
|
524
|
+
deepest = max(bottom for _, bottom, _ in own)
|
|
525
|
+
|
|
526
|
+
def at(depth: float) -> str | None:
|
|
527
|
+
for top, bottom, water_type in own:
|
|
528
|
+
# A boundary belongs to the layer below it, except at the bottom
|
|
529
|
+
# of the deepest layer, which has no layer below: 200 m is still
|
|
530
|
+
# inside the paper's profile.
|
|
531
|
+
if top <= depth < bottom or depth == bottom == deepest:
|
|
532
|
+
return water_type
|
|
533
|
+
# Beyond 200 m the paper makes no statement at all.
|
|
534
|
+
return None
|
|
535
|
+
|
|
536
|
+
if depths.ndim == 0:
|
|
537
|
+
return at(float(depths))
|
|
538
|
+
out = np.empty(depths.shape, dtype=object)
|
|
539
|
+
for index, depth in np.ndenumerate(depths):
|
|
540
|
+
out[index] = at(float(depth))
|
|
541
|
+
return out
|
|
519
542
|
|
|
520
543
|
|
|
521
|
-
|
|
544
|
+
# == on arrays has no single answer; the generated repr printed every array.
|
|
545
|
+
@dataclass(frozen=True, eq=False, repr=False)
|
|
522
546
|
class Descent:
|
|
523
547
|
"""Downwelling irradiance carried down through changing water types."""
|
|
524
548
|
|
|
@@ -534,6 +558,23 @@ class Descent:
|
|
|
534
558
|
transmittance: np.ndarray
|
|
535
559
|
"""``Ed(depth) / Ed(0)``: ``exp(-sum(Kd_i * thickness_i))``."""
|
|
536
560
|
|
|
561
|
+
def __repr__(self) -> str:
|
|
562
|
+
# Adjacent 10 m layers of one type are shown as one span.
|
|
563
|
+
spans: list[list] = []
|
|
564
|
+
for top, bottom, water_type in self.layers:
|
|
565
|
+
if spans and spans[-1][2] == water_type:
|
|
566
|
+
spans[-1][1] = bottom
|
|
567
|
+
else:
|
|
568
|
+
spans.append([top, bottom, water_type])
|
|
569
|
+
path = ", ".join(f"{t} {lo:g}-{hi:g}" for lo, hi, t in spans)
|
|
570
|
+
path = f"{path} m" if path else "no layer crossed"
|
|
571
|
+
n = np.size(self.wavelengths)
|
|
572
|
+
return (
|
|
573
|
+
f"<Descent {self.surface_water_type} to {self.depth_m:g} m: "
|
|
574
|
+
f"{path}; Kd from {self.source}; "
|
|
575
|
+
f"{n} wavelength{'s' if n != 1 else ''}>"
|
|
576
|
+
)
|
|
577
|
+
|
|
537
578
|
|
|
538
579
|
def descend(surface_water_type: str, depth_m: float, wavelengths, *,
|
|
539
580
|
source: str = "jerlov1976") -> Descent:
|
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jerlov
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: Inherent optical properties of Jerlov water types, with provenance
|
|
5
5
|
Author: T. Ishibashi
|
|
6
6
|
License-Expression: Apache-2.0
|
|
7
7
|
Keywords: ocean optics,Jerlov water types,inherent optical properties
|
|
8
|
-
Classifier: Development Status ::
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
9
|
Classifier: Intended Audience :: Science/Research
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
10
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
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
|
|
11
17
|
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
18
|
+
Classifier: Typing :: Typed
|
|
12
19
|
Requires-Python: >=3.10
|
|
13
20
|
Description-Content-Type: text/markdown
|
|
14
21
|
License-File: LICENSE
|
|
15
22
|
License-File: NOTICE
|
|
16
23
|
Requires-Dist: numpy>=1.22
|
|
17
|
-
Provides-Extra: plot
|
|
18
|
-
Requires-Dist: matplotlib; extra == "plot"
|
|
19
24
|
Provides-Extra: test
|
|
20
25
|
Requires-Dist: pytest; extra == "test"
|
|
21
26
|
Provides-Extra: build
|
|
@@ -72,7 +77,8 @@ paper's own constant, so constants are attached to sources.
|
|
|
72
77
|
|
|
73
78
|
## What this package will not do
|
|
74
79
|
|
|
75
|
-
- **Extrapolate.** Asking for a wavelength outside the data raises.
|
|
80
|
+
- **Extrapolate.** Asking for a wavelength outside the data raises. Values
|
|
81
|
+
that a source itself extrapolated are returned, but marked, and they warn.
|
|
76
82
|
- **Fill a gap.** Where a published value is wrong and could not be
|
|
77
83
|
recovered, the value is NaN and stays NaN through interpolation.
|
|
78
84
|
- **Supply bb.** The backscattering coefficient is not determined by the
|
|
@@ -260,6 +266,7 @@ coastal water clears as you go down.
|
|
|
260
266
|
jerlov.water_type_at_depth("I", 60.0) # 'IB'
|
|
261
267
|
jerlov.water_type_at_depth("3C", 45.0) # 'II'
|
|
262
268
|
jerlov.water_type_at_depth("9C", 15.0) # None: the paper declined to say
|
|
269
|
+
jerlov.water_type_at_depth("I", [0, 30, 60]) # array(['I', 'IA', 'IB'], dtype=object)
|
|
263
270
|
```
|
|
264
271
|
|
|
265
272
|
`None` means fewer than ten measurement campaigns supported a declaration, so
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "jerlov"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.8.0"
|
|
8
8
|
description = "Inherent optical properties of Jerlov water types, with provenance"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -14,14 +14,20 @@ license-files = ["LICENSE", "NOTICE"]
|
|
|
14
14
|
authors = [{name = "T. Ishibashi"}]
|
|
15
15
|
keywords = ["ocean optics", "Jerlov water types", "inherent optical properties"]
|
|
16
16
|
classifiers = [
|
|
17
|
-
"Development Status ::
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
18
|
"Intended Audience :: Science/Research",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
19
20
|
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
20
26
|
"Topic :: Scientific/Engineering :: Physics",
|
|
27
|
+
"Typing :: Typed",
|
|
21
28
|
]
|
|
22
29
|
|
|
23
30
|
[project.optional-dependencies]
|
|
24
|
-
plot = ["matplotlib"]
|
|
25
31
|
test = ["pytest"]
|
|
26
32
|
build = ["openpyxl", "colour-science"]
|
|
27
33
|
|
|
@@ -338,6 +338,15 @@ def test_flags_must_cover_every_wavelength():
|
|
|
338
338
|
Water([400.0, 500.0, 600.0], a=[0.1, 0.2, 0.3], flags={"a": ("ok",)})
|
|
339
339
|
|
|
340
340
|
|
|
341
|
+
def test_flags_for_a_quantity_the_water_lacks_are_refused():
|
|
342
|
+
"""They used to be kept and never consulted, so the doubt they recorded
|
|
343
|
+
was lost without a word; "kd" for "Kd" was lost the same way."""
|
|
344
|
+
with pytest.raises(ValueError, match="flags given for 'b'.*it has a"):
|
|
345
|
+
Water([400.0, 500.0], a=[0.1, 0.2], flags={"b": ("suspect", "ok")})
|
|
346
|
+
with pytest.raises(ValueError, match="flags given for 'kd'"):
|
|
347
|
+
Water([400.0, 500.0], kd=[0.1, 0.2], flags={"kd": ("suspect", "ok")})
|
|
348
|
+
|
|
349
|
+
|
|
341
350
|
def test_b_from_c_warns_when_c_is_below_pure_water():
|
|
342
351
|
"""b came out negative without a word; kd_spectrum already warned in the
|
|
343
352
|
same situation."""
|
|
@@ -161,3 +161,32 @@ def test_descent_to_the_surface_is_no_attenuation():
|
|
|
161
161
|
d = jerlov.descend("II", 0.0, [450.0, 550.0])
|
|
162
162
|
assert np.all(d.transmittance == 1.0)
|
|
163
163
|
assert d.layers == ()
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def test_an_array_of_depths_gives_one_type_per_depth():
|
|
167
|
+
depths = np.array([[0.0, 30.0], [60.0, 250.0]])
|
|
168
|
+
types = jerlov.water_type_at_depth("I", depths)
|
|
169
|
+
assert types.shape == depths.shape
|
|
170
|
+
assert types.tolist() == [["I", "IA"], ["IB", None]]
|
|
171
|
+
for depth, got in zip(depths.ravel(), types.ravel()):
|
|
172
|
+
assert got == jerlov.water_type_at_depth("I", float(depth))
|
|
173
|
+
# A list is an array too; a scalar is still a plain string.
|
|
174
|
+
assert list(jerlov.water_type_at_depth("9C", [5, 15])) == ["9C", None]
|
|
175
|
+
assert isinstance(jerlov.water_type_at_depth("I", np.float64(5)), str)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def test_one_bad_depth_in_an_array_refuses_the_whole_array():
|
|
179
|
+
with pytest.raises(ValueError, match="finite"):
|
|
180
|
+
jerlov.water_type_at_depth("I", [10.0, float("nan")])
|
|
181
|
+
with pytest.raises(ValueError, match="cannot be negative"):
|
|
182
|
+
jerlov.water_type_at_depth("I", [10.0, -1.0])
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def test_a_descent_prints_its_path_not_its_arrays():
|
|
186
|
+
"""The generated repr printed every wavelength and transmittance."""
|
|
187
|
+
d = jerlov.descend("1C", 45.0, np.arange(400.0, 701.0, 1.0))
|
|
188
|
+
text = repr(d)
|
|
189
|
+
assert text == ("<Descent 1C to 45 m: 1C 0-20, III 20-40, II 40-45 m; "
|
|
190
|
+
"Kd from jerlov1976; 301 wavelengths>")
|
|
191
|
+
assert repr(jerlov.descend("II", 0.0, 500.0)).startswith(
|
|
192
|
+
"<Descent II to 0 m: no layer crossed;")
|
|
@@ -322,3 +322,50 @@ def test_examples_use_only_the_public_api():
|
|
|
322
322
|
offenders = [p.name for p in ROOT.glob("examples/*.py")
|
|
323
323
|
if "_data" in p.read_text()]
|
|
324
324
|
assert not offenders, f"examples reaching into jerlov._data: {offenders}"
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
@source_tree
|
|
328
|
+
def test_the_python_classifiers_are_the_versions_CI_runs():
|
|
329
|
+
"""PyPI shows these. A version listed but never tested is a claim, as
|
|
330
|
+
section 15 of DECISIONS.md found for numpy; one tested but not listed
|
|
331
|
+
is support nobody can see."""
|
|
332
|
+
toml = (ROOT / "pyproject.toml").read_text()
|
|
333
|
+
listed = set(re.findall(
|
|
334
|
+
r'"Programming Language :: Python :: (3\.\d+)"', toml))
|
|
335
|
+
ci = (ROOT / ".github" / "workflows" / "test.yml").read_text()
|
|
336
|
+
matrix = re.search(r"python-version: \[(.+?)\]", ci).group(1)
|
|
337
|
+
tested = set(re.findall(r'"(3\.\d+)"', matrix))
|
|
338
|
+
assert listed == tested, (
|
|
339
|
+
f"pyproject.toml lists Python {sorted(listed)}, CI tests {sorted(tested)}"
|
|
340
|
+
)
|
|
341
|
+
floor = re.search(r'^requires-python = ">=(3\.\d+)"', toml, re.M).group(1)
|
|
342
|
+
assert floor == min(tested, key=lambda v: int(v.split(".")[1])), (
|
|
343
|
+
f"requires-python says >={floor}, but CI's oldest is {min(tested)}"
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
#: Import name of each distribution an extra installs.
|
|
348
|
+
_IMPORTED_AS = {"pytest": "pytest", "openpyxl": "openpyxl",
|
|
349
|
+
"colour-science": "colour"}
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
@source_tree
|
|
353
|
+
def test_every_optional_extra_is_used_by_something():
|
|
354
|
+
"""The `plot` extra installed matplotlib, which nothing imported."""
|
|
355
|
+
toml = (ROOT / "pyproject.toml").read_text()
|
|
356
|
+
block = re.search(
|
|
357
|
+
r"^\[project\.optional-dependencies\]\n(.*?)(?=^\[)", toml,
|
|
358
|
+
re.M | re.S).group(1)
|
|
359
|
+
code = "\n".join(p.read_text() for d in ("jerlov", "tests", "tools",
|
|
360
|
+
"examples")
|
|
361
|
+
for p in (ROOT / d).rglob("*.py"))
|
|
362
|
+
for extra, spec in re.findall(r"^(\w+) = \[(.*)\]$", block, re.M):
|
|
363
|
+
for dist in re.findall(r'"([A-Za-z0-9_.-]+)', spec):
|
|
364
|
+
assert dist in _IMPORTED_AS, (
|
|
365
|
+
f"extra {extra!r} installs {dist}; add its import name to "
|
|
366
|
+
"_IMPORTED_AS"
|
|
367
|
+
)
|
|
368
|
+
name = _IMPORTED_AS[dist]
|
|
369
|
+
assert re.search(rf"^\s*(import|from) {name}\b", code, re.M), (
|
|
370
|
+
f"extra {extra!r} installs {dist}, which nothing imports"
|
|
371
|
+
)
|
|
@@ -84,9 +84,9 @@ def test_the_alternative_type_I_fit_is_reachable():
|
|
|
84
84
|
assert fifty.fit_depth_m == 50
|
|
85
85
|
assert fifty.water_type == "I"
|
|
86
86
|
assert fifty.R != hundred.R
|
|
87
|
-
# The paper gives it because the profile changes slope;
|
|
88
|
-
#
|
|
89
|
-
assert abs(fifty.fraction_at(
|
|
87
|
+
# The paper gives it because the profile changes slope; they must
|
|
88
|
+
# therefore disagree, even inside the 50 m both were fitted over.
|
|
89
|
+
assert abs(fifty.fraction_at(40.0) - hundred.fraction_at(40.0)) > 1e-4
|
|
90
90
|
|
|
91
91
|
|
|
92
92
|
def test_the_rows_that_are_not_water_types_are_not_offered():
|
|
@@ -130,3 +130,27 @@ def test_table_xxi_is_public():
|
|
|
130
130
|
assert len(jerlov.jerlov1968_solar_fraction(t)[0]) == 12
|
|
131
131
|
with pytest.raises(KeyError, match="known:"):
|
|
132
132
|
jerlov.jerlov1968_solar_fraction("IV")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def test_below_the_fitted_depth_it_warns():
|
|
136
|
+
"""Paulson & Simpson fitted the upper 100 m, and 50 m for I_upper50."""
|
|
137
|
+
import warnings
|
|
138
|
+
|
|
139
|
+
with warnings.catch_warnings():
|
|
140
|
+
warnings.simplefilter("error")
|
|
141
|
+
jerlov.solar_fraction("IB", 100.0)
|
|
142
|
+
jerlov.shortwave_parameters("I_upper50").fraction_at([0.0, 50.0])
|
|
143
|
+
with pytest.warns(jerlov.ProvenanceWarning, match="upper 100 m"):
|
|
144
|
+
jerlov.solar_fraction("IB", 100.5)
|
|
145
|
+
with pytest.warns(jerlov.ProvenanceWarning, match="upper 50 m"):
|
|
146
|
+
jerlov.shortwave_parameters("I_upper50").fraction_at([10.0, 60.0])
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def test_rows_that_are_not_types_carry_no_fit_depth_and_do_not_warn():
|
|
150
|
+
import warnings
|
|
151
|
+
|
|
152
|
+
p = jerlov.shortwave_parameters("run_1", include_non_types=True)
|
|
153
|
+
assert p.fit_depth_m is None
|
|
154
|
+
with warnings.catch_warnings():
|
|
155
|
+
warnings.simplefilter("error")
|
|
156
|
+
p.fraction_at(150.0)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|