jerlov 0.2.0__tar.gz → 0.2.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 (39) hide show
  1. {jerlov-0.2.0/jerlov.egg-info → jerlov-0.2.2}/PKG-INFO +12 -11
  2. {jerlov-0.2.0 → jerlov-0.2.2}/README.md +11 -10
  3. {jerlov-0.2.0 → jerlov-0.2.2}/jerlov/__init__.py +1 -1
  4. {jerlov-0.2.0 → jerlov-0.2.2}/jerlov/colour.py +10 -7
  5. {jerlov-0.2.0 → jerlov-0.2.2}/jerlov/water.py +11 -4
  6. {jerlov-0.2.0 → jerlov-0.2.2/jerlov.egg-info}/PKG-INFO +12 -11
  7. {jerlov-0.2.0 → jerlov-0.2.2}/pyproject.toml +1 -1
  8. {jerlov-0.2.0 → jerlov-0.2.2}/tests/test_api.py +27 -0
  9. {jerlov-0.2.0 → jerlov-0.2.2}/tests/test_colour.py +8 -3
  10. {jerlov-0.2.0 → jerlov-0.2.2}/tests/test_packaging.py +64 -0
  11. {jerlov-0.2.0 → jerlov-0.2.2}/tests/test_reproduces_papers.py +72 -0
  12. {jerlov-0.2.0 → jerlov-0.2.2}/LICENSE +0 -0
  13. {jerlov-0.2.0 → jerlov-0.2.2}/NOTICE +0 -0
  14. {jerlov-0.2.0 → jerlov-0.2.2}/jerlov/_data.py +0 -0
  15. {jerlov-0.2.0 → jerlov-0.2.2}/jerlov/data/__init__.py +0 -0
  16. {jerlov-0.2.0 → jerlov-0.2.2}/jerlov/data/austin1986_kd.csv +0 -0
  17. {jerlov-0.2.0 → jerlov-0.2.2}/jerlov/data/austin1986_model.csv +0 -0
  18. {jerlov-0.2.0 → jerlov-0.2.2}/jerlov/data/cie1931_2deg_cmf.csv +0 -0
  19. {jerlov-0.2.0 → jerlov-0.2.2}/jerlov/data/cie_d65.csv +0 -0
  20. {jerlov-0.2.0 → jerlov-0.2.2}/jerlov/data/jerlov1968_kd.csv +0 -0
  21. {jerlov-0.2.0 → jerlov-0.2.2}/jerlov/data/jerlov1968_total_irradiance.csv +0 -0
  22. {jerlov-0.2.0 → jerlov-0.2.2}/jerlov/data/jerlov1976_kd.csv +0 -0
  23. {jerlov-0.2.0 → jerlov-0.2.2}/jerlov/data/paulson1977_shortwave.csv +0 -0
  24. {jerlov-0.2.0 → jerlov-0.2.2}/jerlov/data/smart2007_b_from_c.csv +0 -0
  25. {jerlov-0.2.0 → jerlov-0.2.2}/jerlov/data/solonenko2015_iop.csv +0 -0
  26. {jerlov-0.2.0 → jerlov-0.2.2}/jerlov/data/williamson2022_iop.csv +0 -0
  27. {jerlov-0.2.0 → jerlov-0.2.2}/jerlov/data/williamson2022_measured.csv +0 -0
  28. {jerlov-0.2.0 → jerlov-0.2.2}/jerlov/data/williamson2023_depth.csv +0 -0
  29. {jerlov-0.2.0 → jerlov-0.2.2}/jerlov/scene.py +0 -0
  30. {jerlov-0.2.0 → jerlov-0.2.2}/jerlov/shortwave.py +0 -0
  31. {jerlov-0.2.0 → jerlov-0.2.2}/jerlov/sources.py +0 -0
  32. {jerlov-0.2.0 → jerlov-0.2.2}/jerlov.egg-info/SOURCES.txt +0 -0
  33. {jerlov-0.2.0 → jerlov-0.2.2}/jerlov.egg-info/dependency_links.txt +0 -0
  34. {jerlov-0.2.0 → jerlov-0.2.2}/jerlov.egg-info/requires.txt +0 -0
  35. {jerlov-0.2.0 → jerlov-0.2.2}/jerlov.egg-info/top_level.txt +0 -0
  36. {jerlov-0.2.0 → jerlov-0.2.2}/setup.cfg +0 -0
  37. {jerlov-0.2.0 → jerlov-0.2.2}/tests/test_depth.py +0 -0
  38. {jerlov-0.2.0 → jerlov-0.2.2}/tests/test_scene.py +0 -0
  39. {jerlov-0.2.0 → jerlov-0.2.2}/tests/test_shortwave.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jerlov
3
- Version: 0.2.0
3
+ Version: 0.2.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
@@ -257,22 +257,23 @@ rather than substituting a neighbour.
257
257
  ## Examples
258
258
 
259
259
  ```
260
- python examples/sources_disagree.py
261
- python examples/synthetic_underwater_images.py
260
+ python examples/sources_disagree.py why the source is an argument
261
+ python examples/from_one_measurement.py from an instrument reading
262
+ python examples/synthetic_underwater_images.py appearance at range and depth
263
+ python examples/solar_heating.py for ocean circulation models
264
+ python examples/what_an_eye_sees.py any spectral sensitivity
262
265
  ```
263
266
 
264
- The first shows why the source is an argument: at 510 nm the scattering
265
- coefficient of Jerlov III differs by a factor of 3.8 between the two published
266
- sets. The second synthesises how reflectance patches appear at range and
267
- depth, white-balanced two ways, and ends with a list of everything it assumed.
268
-
269
- Both are run by CI, so an example that has stopped working is a failed build.
267
+ Five scripts, each aimed at a different reader; `examples/README.md` says
268
+ which to start with. CI runs all of them on every push, so an example that has
269
+ stopped working is a failed build. Every one ends with the assumptions it
270
+ made.
270
271
 
271
272
  ## Provenance and design
272
273
 
273
274
  `DATA.md` records, for every shipped table, where it came from, what was
274
- verified, and what is known to be wrong with it. Fifteen entries are
275
- documented there: seven confirmed defects in the source literature, three
275
+ verified, and what is known to be wrong with it. Seventeen entries are
276
+ documented there: eight confirmed defects in the source literature, three
276
277
  questions the first edition of Jerlov settled, and the rest notes.
277
278
 
278
279
  `DECISIONS.md` records why the package is shaped the way it is, including the
@@ -232,22 +232,23 @@ rather than substituting a neighbour.
232
232
  ## Examples
233
233
 
234
234
  ```
235
- python examples/sources_disagree.py
236
- python examples/synthetic_underwater_images.py
235
+ python examples/sources_disagree.py why the source is an argument
236
+ python examples/from_one_measurement.py from an instrument reading
237
+ python examples/synthetic_underwater_images.py appearance at range and depth
238
+ python examples/solar_heating.py for ocean circulation models
239
+ python examples/what_an_eye_sees.py any spectral sensitivity
237
240
  ```
238
241
 
239
- The first shows why the source is an argument: at 510 nm the scattering
240
- coefficient of Jerlov III differs by a factor of 3.8 between the two published
241
- sets. The second synthesises how reflectance patches appear at range and
242
- depth, white-balanced two ways, and ends with a list of everything it assumed.
243
-
244
- Both are run by CI, so an example that has stopped working is a failed build.
242
+ Five scripts, each aimed at a different reader; `examples/README.md` says
243
+ which to start with. CI runs all of them on every push, so an example that has
244
+ stopped working is a failed build. Every one ends with the assumptions it
245
+ made.
245
246
 
246
247
  ## Provenance and design
247
248
 
248
249
  `DATA.md` records, for every shipped table, where it came from, what was
249
- verified, and what is known to be wrong with it. Fifteen entries are
250
- documented there: seven confirmed defects in the source literature, three
250
+ verified, and what is known to be wrong with it. Seventeen entries are
251
+ documented there: eight confirmed defects in the source literature, three
251
252
  questions the first edition of Jerlov settled, and the rest notes.
252
253
 
253
254
  `DECISIONS.md` records why the package is shaped the way it is, including the
@@ -64,4 +64,4 @@ __all__ = [
64
64
  "MissingQuantityError",
65
65
  ]
66
66
 
67
- __version__ = "0.2.0"
67
+ __version__ = "0.2.2"
@@ -151,15 +151,18 @@ def integrate_response(spectrum, wavelengths, response, response_wavelengths,
151
151
  if wavelengths.size < 2:
152
152
  raise ValueError("at least two wavelengths are needed to integrate")
153
153
 
154
- worst = min(
155
- _coverage(wavelengths, response_wavelengths, response[:, k])
156
- for k in range(response.shape[1])
157
- )
154
+ covered = [_coverage(wavelengths, response_wavelengths, response[:, k])
155
+ for k in range(response.shape[1])]
156
+ worst = min(covered)
158
157
  if worst < 0.999:
158
+ which = (f"channel {covered.index(worst)} of {len(covered)}"
159
+ if len(covered) > 1 else "it")
159
160
  warnings.warn(
160
- f"the spectrum spans {wavelengths[0]:g}-{wavelengths[-1]:g} nm and "
161
- f"covers only {worst:.1%} of the {name}; the integral is over the "
162
- "overlap and is biased by what was left out",
161
+ f"the spectrum spans {wavelengths[0]:g}-{wavelengths[-1]:g} nm; "
162
+ f"of the {name}, {which} is only {worst:.1%} covered "
163
+ f"(all channels: {', '.join(f'{c:.1%}' for c in covered)}). "
164
+ "The integral is over the overlap and is biased by what was left "
165
+ "out",
163
166
  CoverageWarning,
164
167
  stacklevel=2,
165
168
  )
@@ -131,11 +131,18 @@ class Water:
131
131
  values = self._series[quantity]
132
132
  out = np.interp(query, self.wavelengths, values)
133
133
  # np.interp happily bridges a NaN-free path around a NaN, so check the
134
- # bracketing samples explicitly.
134
+ # samples the answer actually rests on. A query that lands exactly on
135
+ # a sample rests on that sample alone: it is not interpolated, so a
136
+ # missing neighbour must not poison it.
135
137
  idx = np.searchsorted(self.wavelengths, query)
136
- for k, i in enumerate(idx):
137
- neighbours = values[max(i - 1, 0):min(i + 1, values.size) + 1]
138
- if np.any(np.isnan(neighbours)):
138
+ for k, (i, w_query) in enumerate(zip(idx, query)):
139
+ exact = i < self.wavelengths.size and self.wavelengths[i] == w_query
140
+ if exact:
141
+ if np.isnan(values[i]):
142
+ out[k] = np.nan
143
+ continue
144
+ left, right = max(i - 1, 0), min(i, values.size - 1)
145
+ if np.any(np.isnan(values[left:right + 1])):
139
146
  out[k] = np.nan
140
147
  return _like_input(out, wl)
141
148
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jerlov
3
- Version: 0.2.0
3
+ Version: 0.2.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
@@ -257,22 +257,23 @@ rather than substituting a neighbour.
257
257
  ## Examples
258
258
 
259
259
  ```
260
- python examples/sources_disagree.py
261
- python examples/synthetic_underwater_images.py
260
+ python examples/sources_disagree.py why the source is an argument
261
+ python examples/from_one_measurement.py from an instrument reading
262
+ python examples/synthetic_underwater_images.py appearance at range and depth
263
+ python examples/solar_heating.py for ocean circulation models
264
+ python examples/what_an_eye_sees.py any spectral sensitivity
262
265
  ```
263
266
 
264
- The first shows why the source is an argument: at 510 nm the scattering
265
- coefficient of Jerlov III differs by a factor of 3.8 between the two published
266
- sets. The second synthesises how reflectance patches appear at range and
267
- depth, white-balanced two ways, and ends with a list of everything it assumed.
268
-
269
- Both are run by CI, so an example that has stopped working is a failed build.
267
+ Five scripts, each aimed at a different reader; `examples/README.md` says
268
+ which to start with. CI runs all of them on every push, so an example that has
269
+ stopped working is a failed build. Every one ends with the assumptions it
270
+ made.
270
271
 
271
272
  ## Provenance and design
272
273
 
273
274
  `DATA.md` records, for every shipped table, where it came from, what was
274
- verified, and what is known to be wrong with it. Fifteen entries are
275
- documented there: seven confirmed defects in the source literature, three
275
+ verified, and what is known to be wrong with it. Seventeen entries are
276
+ documented there: eight confirmed defects in the source literature, three
276
277
  questions the first edition of Jerlov settled, and the rest notes.
277
278
 
278
279
  `DECISIONS.md` records why the package is shaped the way it is, including the
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "jerlov"
7
- version = "0.2.0"
7
+ version = "0.2.2"
8
8
  description = "Inherent optical properties of Jerlov water types, with provenance"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -186,3 +186,30 @@ def test_a_missing_value_is_nan_whether_scalar_or_array():
186
186
  warnings.simplefilter("ignore", ProvenanceWarning)
187
187
  assert np.isnan(w.a(675))
188
188
  assert np.isnan(w.a([675.0])).all()
189
+
190
+
191
+ # -- landing exactly on a sample -----------------------------------------
192
+
193
+
194
+ def test_an_exact_hit_survives_a_missing_neighbour():
195
+ """A query on a sample is not interpolated, so a gap beside it is not its
196
+ problem. Found by checking the shipped 1976 file against the printed
197
+ table: Jerlov 7C has no data below 350 nm, and kd(350) was returning nan
198
+ although 350 nm itself is tabulated.
199
+ """
200
+ w = jerlov.water("7C", source="jerlov1976")
201
+ with warnings.catch_warnings():
202
+ warnings.simplefilter("ignore", ProvenanceWarning)
203
+ assert w.kd(350.0) == pytest.approx(3.0)
204
+ assert np.isnan(w.kd(349.0)) # inside the gap, still nan
205
+ assert np.isnan(w.kd(348.0))
206
+
207
+
208
+ def test_interpolating_across_a_gap_still_gives_nan():
209
+ """The guard this rests on must not have been loosened."""
210
+ w = jerlov.water("5C", source="solonenko2015")
211
+ with warnings.catch_warnings():
212
+ warnings.simplefilter("ignore", ProvenanceWarning)
213
+ assert np.isnan(w.a(650)) # a published value that is wrong
214
+ assert np.isnan(w.a(660)) # and interpolation across it
215
+ assert np.isnan(w.a(675))
@@ -73,7 +73,7 @@ def test_each_primary_lights_only_its_own_channel():
73
73
  def test_a_narrow_spectrum_warns():
74
74
  """450-650 nm looks like a full spectrum but is not."""
75
75
  wl = np.arange(450.0, 651.0, 1.0)
76
- with pytest.warns(CoverageWarning, match="covers only"):
76
+ with pytest.warns(CoverageWarning, match="covered"):
77
77
  spectrum_to_xyz(np.ones_like(wl), wl)
78
78
 
79
79
 
@@ -88,8 +88,13 @@ def test_the_warning_states_what_was_missed():
88
88
  with pytest.warns(CoverageWarning) as caught:
89
89
  spectrum_to_xyz(np.ones_like(wl), wl)
90
90
  message = str(caught[0].message)
91
- assert "500-600 nm" in message
92
- assert "CIE 1931" in message
91
+ assert "500-600 nm" in message # the span it actually had
92
+ assert "CIE 1931" in message # what it was integrated against
93
+ assert "all channels:" in message # not just the worst one
94
+ # z-bar peaks at 445 nm, so 500-600 nm barely touches it. Naming the
95
+ # channel matters: "4.1% covered" alone reads as if the whole observer
96
+ # were 4.1% covered, which would be wrong.
97
+ assert "channel 2 of 3" in message
93
98
 
94
99
 
95
100
  # -- white, the other one ------------------------------------------------
@@ -76,3 +76,67 @@ def test_every_shipped_table_has_a_build_script_or_is_explained():
76
76
  scripts = " ".join(p.read_text() for p in (ROOT / "tools").glob("*.py"))
77
77
  for csv in sorted((ROOT / "jerlov" / "data").glob("*.csv")):
78
78
  assert csv.name in scripts, f"no build script writes {csv.name}"
79
+
80
+
81
+ # -- the documentation counts itself -------------------------------------
82
+
83
+ _SPELLED = ["zero", "one", "two", "three", "four", "five", "six", "seven",
84
+ "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
85
+ "fifteen", "sixteen", "seventeen", "eighteen", "nineteen",
86
+ "twenty", "twenty-one", "twenty-two", "twenty-three",
87
+ "twenty-four", "twenty-five"]
88
+ WORDS = {word: value for value, word in enumerate(_SPELLED)}
89
+ WORDS.update({word.capitalize(): value
90
+ for value, word in enumerate(_SPELLED)})
91
+
92
+
93
+ def _numbered_sections(text: str) -> int:
94
+ return len(re.findall(r"^## \d+\. ", text, re.MULTILINE))
95
+
96
+
97
+ def _confirmed_sections(text: str) -> int:
98
+ return len(re.findall(r"^## \d+\..*\(confirmed", text, re.MULTILINE))
99
+
100
+
101
+ @source_tree
102
+ def test_DATA_md_counts_its_own_sections():
103
+ """The opening summary is written by hand and has drifted three times."""
104
+ text = (ROOT / "DATA.md").read_text()
105
+ match = re.search(r"^(\w+) entries are recorded below\. (\w+) are confirmed",
106
+ text, re.MULTILINE)
107
+ assert match, "DATA.md no longer opens with a countable summary"
108
+ claimed_total, claimed_confirmed = (WORDS[g] for g in match.groups())
109
+ assert claimed_total == _numbered_sections(text)
110
+ assert claimed_confirmed == _confirmed_sections(text)
111
+
112
+
113
+ @source_tree
114
+ def test_the_zenodo_record_will_carry_what_it_should():
115
+ """`.zenodo.json` is only read at archive time, so nothing else checks it."""
116
+ import json
117
+
118
+ record = json.loads((ROOT / ".zenodo.json").read_text())
119
+ assert record["upload_type"] == "software"
120
+ assert record["license"] == "Apache-2.0"
121
+ assert record["creators"], "a record with no author is not citable"
122
+ for creator in record["creators"]:
123
+ assert creator.get("orcid"), (
124
+ f"{creator['name']} has no ORCID, so the record will not attach "
125
+ "to their publication list"
126
+ )
127
+ # Every source the package ships data from should be reachable from the
128
+ # record, not only from DATA.md.
129
+ derived = [r["identifier"] for r in record["related_identifiers"]
130
+ if r["relation"] == "isDerivedFrom"]
131
+ assert len(derived) >= 8, derived
132
+
133
+
134
+ @source_tree
135
+ def test_the_README_agrees_with_DATA_md_on_the_count():
136
+ data = (ROOT / "DATA.md").read_text()
137
+ readme = (ROOT / "README.md").read_text()
138
+ match = re.search(r"(\w+) entries are\s+documented there: (\w+) confirmed",
139
+ readme)
140
+ assert match, "the README no longer states the count"
141
+ assert WORDS[match.group(1)] == _numbered_sections(data)
142
+ assert WORDS[match.group(2)] == _confirmed_sections(data)
@@ -221,3 +221,75 @@ def test_jerlov_type_I_falls_below_pure_sea_water():
221
221
  if j < float(np.interp(nm, wl_m, kw)):
222
222
  below += 1
223
223
  assert below == 9, f"{below} of {len(checked)} wavelengths below Kw"
224
+
225
+
226
+ # -- checked against independent transcriptions ---------------------------
227
+
228
+ #: Jerlov (1976) Table XXVII, Kd x 100 in 1/m, as reprinted by Paglierani et
229
+ #: al. (2023) Table 10 and by Wozniak & Pelevin (1991) Table 1. The two agree
230
+ #: with each other and with the original scan except at IB 700 nm, where
231
+ #: Wozniak & Pelevin print 59; see DATA.md section 16.
232
+ JERLOV1976_TABLE_XXVII = {
233
+ "I": [15, 6.2, 3.8, 2.8, 2.2, 1.9, 1.8, 2.7, 4.3, 6.3, 8.9, 23.5, 30.5,
234
+ 36, 42, 56],
235
+ "IA": [18, 7.8, 5.2, 3.8, 3.1, 2.6, 2.5, 3.2, 4.8, 6.7, 9.4, 24, 31, 37,
236
+ 43, 57],
237
+ "IB": [22, 10, 6.6, 5.1, 4.2, 3.6, 3.3, 4.2, 5.4, 7.2, 9.9, 24.5, 31.5,
238
+ 37.5, 43.5, 58],
239
+ "II": [37, 17.5, 12.2, 9.6, 8.1, 6.8, 6.2, 7.0, 7.6, 8.9, 11.5, 26, 33.5,
240
+ 40, 46.5, 61],
241
+ "III": [65, 32, 22, 18.5, 16, 13.5, 11.6, 11.5, 11.6, 12.0, 14.8, 29.5,
242
+ 37.5, 44.5, 52, 66],
243
+ "1C": [180, 120, 80, 51, 36, 25, 17, 14, 13, 12, 15, 30, 37, 45, 51, 65],
244
+ "3C": [240, 170, 110, 78, 54, 39, 29, 22, 20, 19, 21, 33, 40, 46, 56, 71],
245
+ "5C": [350, 230, 160, 110, 78, 56, 43, 36, 31, 30, 33, 40, 48, 54, 65, 80],
246
+ "7C": [None, 300, 210, 160, 120, 89, 71, 58, 49, 46, 46, 48, 54, 63, 78,
247
+ 92],
248
+ "9C": [None, 390, 300, 240, 190, 160, 123, 99, 78, 63, 58, 60, 65, 76, 92,
249
+ 110],
250
+ }
251
+ TABLE_XXVII_WAVELENGTHS = [310, 350, 375, 400, 425, 450, 475, 500, 525, 550,
252
+ 575, 600, 625, 650, 675, 700]
253
+
254
+
255
+ def test_the_shipped_1976_file_matches_the_printed_table():
256
+ """The Dstl file reaches us second-hand; the printed table does not."""
257
+ w = {t: jerlov.water(t, source="jerlov1976")
258
+ for t in JERLOV1976_TABLE_XXVII}
259
+ checked = 0
260
+ for water_type, row in JERLOV1976_TABLE_XXVII.items():
261
+ for nm, printed in zip(TABLE_XXVII_WAVELENGTHS, row):
262
+ if printed is None:
263
+ continue
264
+ got = w[water_type].kd(float(nm))
265
+ assert got == pytest.approx(printed / 100.0, rel=0.005), (
266
+ f"{water_type} at {nm} nm: shipped {got}, printed "
267
+ f"{printed / 100.0}"
268
+ )
269
+ checked += 1
270
+ assert checked == 158
271
+
272
+
273
+ def test_the_wozniak_pelevin_reprint_differs_at_exactly_one_cell():
274
+ """DATA.md section 16. A standing record of what that reprint got wrong."""
275
+ reprint = {k: list(v) for k, v in JERLOV1976_TABLE_XXVII.items()}
276
+ reprint["IB"][TABLE_XXVII_WAVELENGTHS.index(700)] = 59 # as printed
277
+ differing = [
278
+ (t, nm)
279
+ for t, row in reprint.items()
280
+ for nm, value in zip(TABLE_XXVII_WAVELENGTHS, row)
281
+ if value is not None
282
+ and value != JERLOV1976_TABLE_XXVII[t][
283
+ TABLE_XXVII_WAVELENGTHS.index(nm)]
284
+ ]
285
+ assert differing == [("IB", 700)]
286
+
287
+
288
+ def test_the_stated_unit_of_that_reprint_is_impossible():
289
+ """Its header says 1e-3 m^-1, which puts Jerlov I below pure sea water."""
290
+ printed = JERLOV1976_TABLE_XXVII["I"][TABLE_XXVII_WAVELENGTHS.index(475)]
291
+ as_stated = printed * 1e-3
292
+ correct = printed * 1e-2
293
+ pure_water_absorption_at_475 = 0.011 # Austin & Petzold Kw, 1/m
294
+ assert as_stated < pure_water_absorption_at_475
295
+ assert correct > pure_water_absorption_at_475
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