jerlov 0.7.1__tar.gz → 0.7.2__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 (49) hide show
  1. {jerlov-0.7.1/jerlov.egg-info → jerlov-0.7.2}/PKG-INFO +2 -2
  2. {jerlov-0.7.1 → jerlov-0.7.2}/README.md +1 -1
  3. {jerlov-0.7.1 → jerlov-0.7.2}/jerlov/__init__.py +1 -1
  4. {jerlov-0.7.1 → jerlov-0.7.2}/jerlov/backscattering.py +8 -1
  5. {jerlov-0.7.1 → jerlov-0.7.2}/jerlov/scene.py +4 -2
  6. {jerlov-0.7.1 → jerlov-0.7.2}/jerlov/water.py +8 -1
  7. {jerlov-0.7.1 → jerlov-0.7.2/jerlov.egg-info}/PKG-INFO +2 -2
  8. {jerlov-0.7.1 → jerlov-0.7.2}/pyproject.toml +1 -1
  9. {jerlov-0.7.1 → jerlov-0.7.2}/tests/test_api.py +30 -0
  10. {jerlov-0.7.1 → jerlov-0.7.2}/tests/test_backscattering.py +12 -0
  11. {jerlov-0.7.1 → jerlov-0.7.2}/tests/test_quoted_figures.py +25 -1
  12. {jerlov-0.7.1 → jerlov-0.7.2}/tests/test_scene.py +8 -0
  13. {jerlov-0.7.1 → jerlov-0.7.2}/LICENSE +0 -0
  14. {jerlov-0.7.1 → jerlov-0.7.2}/NOTICE +0 -0
  15. {jerlov-0.7.1 → jerlov-0.7.2}/jerlov/_data.py +0 -0
  16. {jerlov-0.7.1 → jerlov-0.7.2}/jerlov/colour.py +0 -0
  17. {jerlov-0.7.1 → jerlov-0.7.2}/jerlov/data/__init__.py +0 -0
  18. {jerlov-0.7.1 → jerlov-0.7.2}/jerlov/data/austin1986_kd.csv +0 -0
  19. {jerlov-0.7.1 → jerlov-0.7.2}/jerlov/data/austin1986_model.csv +0 -0
  20. {jerlov-0.7.1 → jerlov-0.7.2}/jerlov/data/boss2001_chi.csv +0 -0
  21. {jerlov-0.7.1 → jerlov-0.7.2}/jerlov/data/cie1931_2deg_cmf.csv +0 -0
  22. {jerlov-0.7.1 → jerlov-0.7.2}/jerlov/data/cie_d65.csv +0 -0
  23. {jerlov-0.7.1 → jerlov-0.7.2}/jerlov/data/jerlov1968_kd.csv +0 -0
  24. {jerlov-0.7.1 → jerlov-0.7.2}/jerlov/data/jerlov1968_total_irradiance.csv +0 -0
  25. {jerlov-0.7.1 → jerlov-0.7.2}/jerlov/data/jerlov1976_kd.csv +0 -0
  26. {jerlov-0.7.1 → jerlov-0.7.2}/jerlov/data/paulson1977_shortwave.csv +0 -0
  27. {jerlov-0.7.1 → jerlov-0.7.2}/jerlov/data/petzold1972_stations.csv +0 -0
  28. {jerlov-0.7.1 → jerlov-0.7.2}/jerlov/data/petzold1972_vsf.csv +0 -0
  29. {jerlov-0.7.1 → jerlov-0.7.2}/jerlov/data/smart2007_b_from_c.csv +0 -0
  30. {jerlov-0.7.1 → jerlov-0.7.2}/jerlov/data/solonenko2015_iop.csv +0 -0
  31. {jerlov-0.7.1 → jerlov-0.7.2}/jerlov/data/williamson2022_aw.csv +0 -0
  32. {jerlov-0.7.1 → jerlov-0.7.2}/jerlov/data/williamson2022_iop.csv +0 -0
  33. {jerlov-0.7.1 → jerlov-0.7.2}/jerlov/data/williamson2022_measured.csv +0 -0
  34. {jerlov-0.7.1 → jerlov-0.7.2}/jerlov/data/williamson2023_depth.csv +0 -0
  35. {jerlov-0.7.1 → jerlov-0.7.2}/jerlov/petzold.py +0 -0
  36. {jerlov-0.7.1 → jerlov-0.7.2}/jerlov/py.typed +0 -0
  37. {jerlov-0.7.1 → jerlov-0.7.2}/jerlov/shortwave.py +0 -0
  38. {jerlov-0.7.1 → jerlov-0.7.2}/jerlov/sources.py +0 -0
  39. {jerlov-0.7.1 → jerlov-0.7.2}/jerlov.egg-info/SOURCES.txt +0 -0
  40. {jerlov-0.7.1 → jerlov-0.7.2}/jerlov.egg-info/dependency_links.txt +0 -0
  41. {jerlov-0.7.1 → jerlov-0.7.2}/jerlov.egg-info/requires.txt +0 -0
  42. {jerlov-0.7.1 → jerlov-0.7.2}/jerlov.egg-info/top_level.txt +0 -0
  43. {jerlov-0.7.1 → jerlov-0.7.2}/setup.cfg +0 -0
  44. {jerlov-0.7.1 → jerlov-0.7.2}/tests/test_colour.py +0 -0
  45. {jerlov-0.7.1 → jerlov-0.7.2}/tests/test_depth.py +0 -0
  46. {jerlov-0.7.1 → jerlov-0.7.2}/tests/test_packaging.py +0 -0
  47. {jerlov-0.7.1 → jerlov-0.7.2}/tests/test_petzold.py +0 -0
  48. {jerlov-0.7.1 → jerlov-0.7.2}/tests/test_reproduces_papers.py +0 -0
  49. {jerlov-0.7.1 → jerlov-0.7.2}/tests/test_shortwave.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jerlov
3
- Version: 0.7.1
3
+ Version: 0.7.2
4
4
  Summary: Inherent optical properties of Jerlov water types, with provenance
5
5
  Author: T. Ishibashi
6
6
  License-Expression: Apache-2.0
@@ -62,7 +62,7 @@ w.a(550), w.b(550), w.c(550)
62
62
  The same Jerlov water type has different coefficients in different papers, and
63
63
  the differences are not small. Solonenko & Mobley (2015) obtained a and b by
64
64
  inverting Kd; Williamson & Hollins (2022) measured them. At 510 nm their
65
- scattering coefficients differ by up to a factor of 2.6.
65
+ scattering coefficients differ by up to a factor of 3.8, at Jerlov III.
66
66
 
67
67
  The equations differ too. Both papers use the scattering model of Haltrin
68
68
  (1999), but with different constants: Haltrin's Eq. (6) gives a
@@ -37,7 +37,7 @@ w.a(550), w.b(550), w.c(550)
37
37
  The same Jerlov water type has different coefficients in different papers, and
38
38
  the differences are not small. Solonenko & Mobley (2015) obtained a and b by
39
39
  inverting Kd; Williamson & Hollins (2022) measured them. At 510 nm their
40
- scattering coefficients differ by up to a factor of 2.6.
40
+ scattering coefficients differ by up to a factor of 3.8, at Jerlov III.
41
41
 
42
42
  The equations differ too. Both papers use the scattering model of Haltrin
43
43
  (1999), but with different constants: Haltrin's Eq. (6) gives a
@@ -96,4 +96,4 @@ __all__ = [
96
96
  "MissingQuantityError",
97
97
  ]
98
98
 
99
- __version__ = "0.7.1"
99
+ __version__ = "0.7.2"
@@ -67,7 +67,14 @@ def _amplitude(wavelength_nm, salinity_psu) -> np.ndarray:
67
67
  f"salinity_psu must be a finite number, 0 or more, not "
68
68
  f"{salinity_psu!r}"
69
69
  )
70
- return (1.38 * (np.asarray(wavelength_nm, dtype=float) / 500.0) ** -4.32
70
+ wavelength = np.asarray(wavelength_nm, dtype=float)
71
+ if not np.all(np.isfinite(wavelength)) or np.any(wavelength <= 0):
72
+ # Zero gave inf and a negative wavelength NaN, each with nothing but
73
+ # a NumPy RuntimeWarning.
74
+ raise ValueError(
75
+ f"wavelength_nm must be a positive number, not {wavelength_nm!r}"
76
+ )
77
+ return (1.38 * (wavelength / 500.0) ** -4.32
71
78
  * (1 + 0.3 * salinity / 37.0) * 1e-4)
72
79
 
73
80
 
@@ -195,10 +195,12 @@ class Scene:
195
195
  "the water must carry both a and b; c = a + b drives the path"
196
196
  )
197
197
  self.water = water
198
- self.wavelengths = np.asarray(wavelengths, dtype=float)
198
+ # Copies, so that changing the caller's arrays afterwards cannot
199
+ # leave the wavelengths out of step with c, evaluated once below.
200
+ self.wavelengths = np.array(wavelengths, dtype=float)
199
201
  if self.wavelengths.ndim != 1 or self.wavelengths.size == 0:
200
202
  raise ValueError("wavelengths must be a non-empty 1-D array")
201
- self.downwelling = np.asarray(downwelling, dtype=float)
203
+ self.downwelling = np.array(downwelling, dtype=float)
202
204
  if self.downwelling.shape != self.wavelengths.shape:
203
205
  raise ValueError("downwelling must have the same shape as wavelengths")
204
206
  if np.any(self.downwelling < 0):
@@ -240,6 +240,9 @@ class Water:
240
240
  f"(available: {available})"
241
241
  )
242
242
  query = _as_array(wl)
243
+ # NaN < lo is False, so a NaN wavelength passed the range check and
244
+ # came back as NaN, warned about the last sample of the table.
245
+ _require_number(query, "wavelength")
243
246
  lo, hi = self.range_nm
244
247
  if np.any(query < lo) or np.any(query > hi):
245
248
  raise ValueError(
@@ -591,7 +594,8 @@ def descend(surface_water_type: str, depth_m: float, wavelengths, *,
591
594
  f"{deepest:g} m, so there is no type to descend through to "
592
595
  f"{depth_m:g} m"
593
596
  )
594
- query = _as_array(wavelengths)
597
+ # A copy: it is frozen below, and the caller's array must not be.
598
+ query = np.array(wavelengths, dtype=float, ndmin=1)
595
599
  optical_depth = np.zeros_like(query)
596
600
  layers: list[tuple[float, float, str]] = []
597
601
  kd: dict[str, np.ndarray] = {}
@@ -723,6 +727,8 @@ def kd_spectrum(kd, wavelength_nm: float, at):
723
727
  )
724
728
  wavelength_nm = float(wavelength_nm)
725
729
  query = _as_array(at)
730
+ _require_number(wavelength_nm, "wavelength_nm")
731
+ _require_number(query, "at")
726
732
  for value, label in ((wavelength_nm, "wavelength_nm"), (query, "at")):
727
733
  if np.any(np.asarray(value) < lo) or np.any(np.asarray(value) > hi):
728
734
  raise ValueError(f"{label} outside the model range ({lo:g}-{hi:g} nm)")
@@ -800,6 +806,7 @@ def b_from_c(c, wavelength_nm, *, bw, cw, bound: str = "average"):
800
806
  if bound not in ratios:
801
807
  raise ValueError(f"bound must be one of {sorted(ratios)}")
802
808
  query = _as_array(wavelength_nm)
809
+ _require_number(query, "wavelength_nm")
803
810
  if np.any(query < wl[0]) or np.any(query > wl[-1]):
804
811
  raise ValueError(
805
812
  f"wavelength outside the measured range ({wl[0]:g}-{wl[-1]:g} nm)"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jerlov
3
- Version: 0.7.1
3
+ Version: 0.7.2
4
4
  Summary: Inherent optical properties of Jerlov water types, with provenance
5
5
  Author: T. Ishibashi
6
6
  License-Expression: Apache-2.0
@@ -62,7 +62,7 @@ w.a(550), w.b(550), w.c(550)
62
62
  The same Jerlov water type has different coefficients in different papers, and
63
63
  the differences are not small. Solonenko & Mobley (2015) obtained a and b by
64
64
  inverting Kd; Williamson & Hollins (2022) measured them. At 510 nm their
65
- scattering coefficients differ by up to a factor of 2.6.
65
+ scattering coefficients differ by up to a factor of 3.8, at Jerlov III.
66
66
 
67
67
  The equations differ too. Both papers use the scattering model of Haltrin
68
68
  (1999), but with different constants: Haltrin's Eq. (6) gives a
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "jerlov"
7
- version = "0.7.1"
7
+ version = "0.7.2"
8
8
  description = "Inherent optical properties of Jerlov water types, with provenance"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -588,3 +588,33 @@ def test_c_warns_once_for_a_wavelength_flagged_in_both_a_and_b():
588
588
  assert messages[0].startswith("c for Jerlov 3C")
589
589
  assert "a missing at 675 nm" in messages[0]
590
590
  assert "b reconstructed at 675 nm" in messages[0]
591
+
592
+
593
+ # -- 0.7.2: inputs that slipped through -------------------------------------
594
+
595
+ @pytest.mark.parametrize("call", [
596
+ lambda: jerlov.water("III").a(float("nan")),
597
+ lambda: jerlov.water("III").c([500.0, float("nan")]),
598
+ lambda: jerlov.water("I", source="jerlov1976").kd(float("nan")),
599
+ lambda: jerlov.water("III", source="solonenko2015").kd_hydrolight(
600
+ float("nan")),
601
+ lambda: jerlov.kd_spectrum(0.06, 490.0, float("nan")),
602
+ lambda: jerlov.kd_spectrum(0.06, float("nan"), 500.0),
603
+ lambda: jerlov.b_from_c(0.5, float("nan"), bw=0.002, cw=0.066),
604
+ ])
605
+ def test_a_wavelength_that_is_not_a_number_is_refused(call):
606
+ """NaN < lo is False, so NaN passed the range check, came back as NaN,
607
+ and warned about the last sample of the table instead."""
608
+ with warnings.catch_warnings():
609
+ warnings.simplefilter("error") # and no misleading warning
610
+ with pytest.raises(ValueError, match="NaN"):
611
+ call()
612
+
613
+
614
+ def test_descend_leaves_the_callers_array_alone():
615
+ wl = np.array([450.0, 550.0])
616
+ d = jerlov.descend("II", 30.0, wl)
617
+ assert wl.flags.writeable
618
+ wl[0] = 999.0
619
+ assert d.wavelengths[0] == 450.0
620
+ assert not d.wavelengths.flags.writeable
@@ -216,3 +216,15 @@ def test_a_salinity_that_is_not_one_is_refused():
216
216
  with pytest.raises(ValueError, match="salinity_psu"):
217
217
  call(bad)
218
218
  jerlov.pure_water_vsf(120.0, 532.0, 0.0) # fresh water is fine
219
+
220
+
221
+ def test_a_wavelength_that_is_not_one_is_refused():
222
+ for bad in (0.0, -500.0, float("nan"), float("inf")):
223
+ for call in (lambda w: jerlov.pure_water_vsf(120.0, w),
224
+ lambda w: jerlov.pure_water_backscattering(w),
225
+ lambda w: jerlov.pure_water_scattering(w),
226
+ lambda w: jerlov.bb_from_vsf(0.0021, 140.0, w)):
227
+ with warnings.catch_warnings():
228
+ warnings.simplefilter("error")
229
+ with pytest.raises(ValueError, match="wavelength_nm"):
230
+ call(bad)
@@ -37,7 +37,7 @@ def quiet():
37
37
 
38
38
 
39
39
  def test_scattering_at_510nm_differs_by_the_stated_factor():
40
- """README and sources_disagree.py both say 3.8 for Jerlov III."""
40
+ """sources_disagree.py says 3.8 for Jerlov III."""
41
41
  with quiet():
42
42
  warnings.simplefilter("ignore")
43
43
  measured = float(jerlov.water("III").b(510))
@@ -45,6 +45,30 @@ def test_scattering_at_510nm_differs_by_the_stated_factor():
45
45
  assert inverted / measured == pytest.approx(3.8, abs=0.05)
46
46
 
47
47
 
48
+ @source_tree
49
+ def test_the_readme_states_the_largest_disagreement_at_510nm():
50
+ """The README said "up to a factor of 2.6" while this test's docstring
51
+ claimed it said 3.8; nothing read the README, so nothing noticed."""
52
+ from jerlov.sources import SOURCES
53
+
54
+ readme = (ROOT / "README.md").read_text()
55
+ match = re.search(r"differ by up to a factor of ([\d.]+), at Jerlov (\w+)",
56
+ readme)
57
+ assert match, "the README no longer states the factor at 510 nm"
58
+ common = set(SOURCES["williamson2022"].water_types) & set(
59
+ SOURCES["solonenko2015"].water_types)
60
+ with quiet():
61
+ warnings.simplefilter("ignore")
62
+ factors = {}
63
+ for t in common:
64
+ one = float(jerlov.water(t).b(510))
65
+ other = float(jerlov.water(t, source="solonenko2015").b(510))
66
+ factors[t] = max(one, other) / min(one, other)
67
+ worst = max(factors, key=factors.get)
68
+ assert worst == match.group(2)
69
+ assert factors[worst] == pytest.approx(float(match.group(1)), abs=0.05)
70
+
71
+
48
72
  def test_the_disagreement_does_not_run_one_way():
49
73
  """sources_disagree.py claims Jerlov IB goes the other direction."""
50
74
  with quiet():
@@ -353,3 +353,11 @@ def test_an_observation_with_no_light_has_a_quiet_repr():
353
353
  with warnings.catch_warnings():
354
354
  warnings.simplefilter("error")
355
355
  assert "veiling undefined" in repr(o)
356
+
357
+
358
+ def test_a_scene_does_not_follow_later_changes_to_the_callers_arrays():
359
+ wl, ed = WL.copy(), np.ones(WL.size)
360
+ s = Scene(jerlov.water("II"), ed, wl)
361
+ wl[0], ed[0] = 999.0, -1.0
362
+ assert s.wavelengths[0] == WL[0]
363
+ assert s.downwelling[0] == 1.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