jerlov 0.9.2__tar.gz → 0.9.4__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.9.2/jerlov.egg-info → jerlov-0.9.4}/PKG-INFO +21 -4
- {jerlov-0.9.2 → jerlov-0.9.4}/README.md +20 -3
- {jerlov-0.9.2 → jerlov-0.9.4}/jerlov/__init__.py +14 -2
- jerlov-0.9.4/jerlov/data/jerlov1977_quanta.csv +203 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/jerlov/par.py +127 -8
- {jerlov-0.9.2 → jerlov-0.9.4}/jerlov/water.py +111 -5
- {jerlov-0.9.2 → jerlov-0.9.4/jerlov.egg-info}/PKG-INFO +21 -4
- {jerlov-0.9.2 → jerlov-0.9.4}/jerlov.egg-info/SOURCES.txt +2 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/pyproject.toml +1 -1
- {jerlov-0.9.2 → jerlov-0.9.4}/tests/test_classify.py +30 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/tests/test_depth.py +47 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/tests/test_packaging.py +26 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/tests/test_petzold.py +4 -1
- jerlov-0.9.4/tests/test_quanta.py +168 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/LICENSE +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/NOTICE +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/jerlov/_data.py +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/jerlov/backscattering.py +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/jerlov/colour.py +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/jerlov/data/__init__.py +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/jerlov/data/austin1986_kd.csv +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/jerlov/data/austin1986_model.csv +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/jerlov/data/boss2001_chi.csv +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/jerlov/data/cie1931_2deg_cmf.csv +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/jerlov/data/cie_d65.csv +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/jerlov/data/jerlov1968_kd.csv +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/jerlov/data/jerlov1968_total_irradiance.csv +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/jerlov/data/jerlov1976_kd.csv +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/jerlov/data/paulson1977_shortwave.csv +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/jerlov/data/petzold1972_stations.csv +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/jerlov/data/petzold1972_vsf.csv +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/jerlov/data/smart2007_b_from_c.csv +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/jerlov/data/solonenko2015_iop.csv +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/jerlov/data/williamson2022_aw.csv +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/jerlov/data/williamson2022_iop.csv +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/jerlov/data/williamson2022_measured.csv +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/jerlov/data/williamson2023_depth.csv +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/jerlov/petzold.py +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/jerlov/py.typed +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/jerlov/scene.py +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/jerlov/shortwave.py +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/jerlov/sources.py +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/jerlov.egg-info/dependency_links.txt +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/jerlov.egg-info/requires.txt +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/jerlov.egg-info/top_level.txt +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/setup.cfg +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/tests/test_api.py +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/tests/test_backscattering.py +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/tests/test_colour.py +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/tests/test_par.py +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/tests/test_quoted_figures.py +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/tests/test_reproduces_papers.py +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/tests/test_scene.py +0 -0
- {jerlov-0.9.2 → jerlov-0.9.4}/tests/test_shortwave.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jerlov
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.4
|
|
4
4
|
Summary: Inherent optical properties of Jerlov water types, with provenance
|
|
5
5
|
Author: T. Ishibashi
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -285,6 +285,9 @@ scene = jerlov.Scene.at_depth(jerlov.water("II"), 45.0, surface, wl, kd=d)
|
|
|
285
285
|
|
|
286
286
|
It refuses to go through a layer the paper declared nothing for, rather than
|
|
287
287
|
carrying the last type on: 3C stops at 70 m, 9C at 10 m.
|
|
288
|
+
`jerlov.profile_depth("3C")` says how far a descent can go, and
|
|
289
|
+
`d.transmittance_at(z)` gives any depth above the descent's bottom without
|
|
290
|
+
descending again.
|
|
288
291
|
|
|
289
292
|
## Light for photosynthesis
|
|
290
293
|
|
|
@@ -304,6 +307,18 @@ default, because PAR counts photons and an energy spectrum read as photons is
|
|
|
304
307
|
weighted wrongly with no sign of it. This is planar PAR, from Ed; PAR on
|
|
305
308
|
scalar irradiance is larger by a factor this package cannot supply.
|
|
306
309
|
|
|
310
|
+
Jerlov measured quanta irradiance, 350-700 nm, by water type, and his
|
|
311
|
+
tables are shipped for checking against:
|
|
312
|
+
|
|
313
|
+
```python
|
|
314
|
+
jerlov.jerlov1977_quanta("II") # <QuantaLevels II: 30% 10.5 m, 10% 24 m, 3% 39 m, 1% 52 m; ...>
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
From II to 3C the package agrees with them within 2 m at every level. For
|
|
318
|
+
Jerlov I it agrees only through `descend`; for IB it puts the 1 percent
|
|
319
|
+
level a third too deep, because Jerlov's IB water grew more turbid below
|
|
320
|
+
15 m. DATA.md section 22.
|
|
321
|
+
|
|
307
322
|
## Which type is my water nearest?
|
|
308
323
|
|
|
309
324
|
```python
|
|
@@ -315,7 +330,9 @@ r.beyond # "clearer" or "more turbid" than every type, or None
|
|
|
315
330
|
|
|
316
331
|
It ranks the types of one edition; it draws no boundaries, since those differ
|
|
317
332
|
between editions. A single wavelength is accepted, and ranks oceanic and
|
|
318
|
-
coastal types less well apart than a spectrum does.
|
|
333
|
+
coastal types less well apart than a spectrum does. `types=` compares with
|
|
334
|
+
some of the types only, which keeps the wavelengths where only the others
|
|
335
|
+
have gaps.
|
|
319
336
|
|
|
320
337
|
## Other entry points
|
|
321
338
|
|
|
@@ -389,8 +406,8 @@ made.
|
|
|
389
406
|
## Provenance and design
|
|
390
407
|
|
|
391
408
|
`DATA.md` records, for every shipped table, where it came from, what was
|
|
392
|
-
verified, and what is known to be wrong with it. Twenty-
|
|
393
|
-
documented there:
|
|
409
|
+
verified, and what is known to be wrong with it. Twenty-two entries are
|
|
410
|
+
documented there: nine confirmed defects in the source literature, three
|
|
394
411
|
questions the first edition of Jerlov settled, and the rest notes.
|
|
395
412
|
|
|
396
413
|
`DECISIONS.md` records why the package is shaped the way it is, including the
|
|
@@ -255,6 +255,9 @@ scene = jerlov.Scene.at_depth(jerlov.water("II"), 45.0, surface, wl, kd=d)
|
|
|
255
255
|
|
|
256
256
|
It refuses to go through a layer the paper declared nothing for, rather than
|
|
257
257
|
carrying the last type on: 3C stops at 70 m, 9C at 10 m.
|
|
258
|
+
`jerlov.profile_depth("3C")` says how far a descent can go, and
|
|
259
|
+
`d.transmittance_at(z)` gives any depth above the descent's bottom without
|
|
260
|
+
descending again.
|
|
258
261
|
|
|
259
262
|
## Light for photosynthesis
|
|
260
263
|
|
|
@@ -274,6 +277,18 @@ default, because PAR counts photons and an energy spectrum read as photons is
|
|
|
274
277
|
weighted wrongly with no sign of it. This is planar PAR, from Ed; PAR on
|
|
275
278
|
scalar irradiance is larger by a factor this package cannot supply.
|
|
276
279
|
|
|
280
|
+
Jerlov measured quanta irradiance, 350-700 nm, by water type, and his
|
|
281
|
+
tables are shipped for checking against:
|
|
282
|
+
|
|
283
|
+
```python
|
|
284
|
+
jerlov.jerlov1977_quanta("II") # <QuantaLevels II: 30% 10.5 m, 10% 24 m, 3% 39 m, 1% 52 m; ...>
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
From II to 3C the package agrees with them within 2 m at every level. For
|
|
288
|
+
Jerlov I it agrees only through `descend`; for IB it puts the 1 percent
|
|
289
|
+
level a third too deep, because Jerlov's IB water grew more turbid below
|
|
290
|
+
15 m. DATA.md section 22.
|
|
291
|
+
|
|
277
292
|
## Which type is my water nearest?
|
|
278
293
|
|
|
279
294
|
```python
|
|
@@ -285,7 +300,9 @@ r.beyond # "clearer" or "more turbid" than every type, or None
|
|
|
285
300
|
|
|
286
301
|
It ranks the types of one edition; it draws no boundaries, since those differ
|
|
287
302
|
between editions. A single wavelength is accepted, and ranks oceanic and
|
|
288
|
-
coastal types less well apart than a spectrum does.
|
|
303
|
+
coastal types less well apart than a spectrum does. `types=` compares with
|
|
304
|
+
some of the types only, which keeps the wavelengths where only the others
|
|
305
|
+
have gaps.
|
|
289
306
|
|
|
290
307
|
## Other entry points
|
|
291
308
|
|
|
@@ -359,8 +376,8 @@ made.
|
|
|
359
376
|
## Provenance and design
|
|
360
377
|
|
|
361
378
|
`DATA.md` records, for every shipped table, where it came from, what was
|
|
362
|
-
verified, and what is known to be wrong with it. Twenty-
|
|
363
|
-
documented there:
|
|
379
|
+
verified, and what is known to be wrong with it. Twenty-two entries are
|
|
380
|
+
documented there: nine confirmed defects in the source literature, three
|
|
364
381
|
questions the first edition of Jerlov settled, and the rest notes.
|
|
365
382
|
|
|
366
383
|
`DECISIONS.md` records why the package is shaped the way it is, including the
|
|
@@ -23,7 +23,14 @@ from .colour import (
|
|
|
23
23
|
spectrum_to_xyz,
|
|
24
24
|
xyz_to_srgb,
|
|
25
25
|
)
|
|
26
|
-
from .par import
|
|
26
|
+
from .par import (
|
|
27
|
+
PAR_RANGE_NM,
|
|
28
|
+
QUANTA_BAND_NM,
|
|
29
|
+
ParProfile,
|
|
30
|
+
QuantaLevels,
|
|
31
|
+
jerlov1977_quanta,
|
|
32
|
+
par_profile,
|
|
33
|
+
)
|
|
27
34
|
from .petzold import PETZOLD_STATIONS, ScatteringFunction, petzold_scattering
|
|
28
35
|
from .scene import (
|
|
29
36
|
AttenuationCoefficients,
|
|
@@ -50,6 +57,7 @@ from .water import (
|
|
|
50
57
|
descend,
|
|
51
58
|
kd_spectrum,
|
|
52
59
|
measured_points,
|
|
60
|
+
profile_depth,
|
|
53
61
|
pure_water_absorption,
|
|
54
62
|
water,
|
|
55
63
|
water_type_at_depth,
|
|
@@ -75,12 +83,16 @@ __all__ = [
|
|
|
75
83
|
"water",
|
|
76
84
|
"water_type_at_depth",
|
|
77
85
|
"descend",
|
|
86
|
+
"profile_depth",
|
|
78
87
|
"Descent",
|
|
79
88
|
"classify_kd",
|
|
80
89
|
"KdClassification",
|
|
81
90
|
"par_profile",
|
|
82
91
|
"ParProfile",
|
|
83
92
|
"PAR_RANGE_NM",
|
|
93
|
+
"jerlov1977_quanta",
|
|
94
|
+
"QuantaLevels",
|
|
95
|
+
"QUANTA_BAND_NM",
|
|
84
96
|
"pure_water_absorption",
|
|
85
97
|
"petzold_scattering",
|
|
86
98
|
"PETZOLD_STATIONS",
|
|
@@ -104,4 +116,4 @@ __all__ = [
|
|
|
104
116
|
"MissingQuantityError",
|
|
105
117
|
]
|
|
106
118
|
|
|
107
|
-
__version__ = "0.9.
|
|
119
|
+
__version__ = "0.9.4"
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
table,water_type,depth_top_m,depth_bottom_m,percent_top,percent_bottom,value,unit,status,note
|
|
2
|
+
2,I,19,19,30,30,19,m,ok,
|
|
3
|
+
2,I,49,49,10,10,49,m,ok,
|
|
4
|
+
2,I,79,79,3,3,79,m,ok,
|
|
5
|
+
2,I,103,103,1,1,103,m,ok,
|
|
6
|
+
2n,I,,,30,30,0.39,z/z(10),ok,
|
|
7
|
+
2n,I,,,3,3,1.65,z/z(10),suspect,"does not follow from the depths in Table 2, even allowing for rounding (DATA.md section 22)"
|
|
8
|
+
2n,I,,,1,1,2.15,z/z(10),suspect,"does not follow from the depths in Table 2, even allowing for rounding (DATA.md section 22)"
|
|
9
|
+
3,I,0,0,,,100,percent,ok,
|
|
10
|
+
3,I,2,2,,,79,percent,ok,
|
|
11
|
+
3,I,5,5,,,64,percent,ok,
|
|
12
|
+
3,I,10,10,,,46,percent,ok,
|
|
13
|
+
3,I,15,15,,,35.5,percent,ok,
|
|
14
|
+
3,I,20,20,,,29,percent,ok,
|
|
15
|
+
3,I,30,30,,,20,percent,ok,
|
|
16
|
+
3,I,40,40,,,13.7,percent,ok,
|
|
17
|
+
3,I,50,50,,,9.5,percent,ok,
|
|
18
|
+
3,I,60,60,,,6.5,percent,ok,
|
|
19
|
+
3,I,70,70,,,4.3,percent,ok,
|
|
20
|
+
3,I,80,80,,,2.9,percent,ok,
|
|
21
|
+
3,I,90,90,,,1.85,percent,ok,
|
|
22
|
+
3,I,100,100,,,1.14,percent,ok,
|
|
23
|
+
4,I,0,19,100,30,0.063,1/m,ok,
|
|
24
|
+
4,I,19,49,30,10,0.037,1/m,ok,
|
|
25
|
+
4,I,49,79,10,3,0.04,1/m,ok,
|
|
26
|
+
4,I,79,103,3,1,0.046,1/m,ok,
|
|
27
|
+
5,I,0,2,,,0.118,1/m,ok,
|
|
28
|
+
5,I,2,5,,,0.073,1/m,ok,
|
|
29
|
+
5,I,5,10,,,0.065,1/m,ok,
|
|
30
|
+
5,I,10,15,,,0.052,1/m,ok,
|
|
31
|
+
5,I,15,20,,,0.04,1/m,ok,
|
|
32
|
+
5,I,20,30,,,0.039,1/m,ok,
|
|
33
|
+
5,I,30,40,,,0.039,1/m,ok,
|
|
34
|
+
5,I,40,50,,,0.037,1/m,ok,
|
|
35
|
+
5,I,50,60,,,0.038,1/m,ok,
|
|
36
|
+
5,I,60,70,,,0.04,1/m,ok,
|
|
37
|
+
5,I,70,80,,,0.04,1/m,ok,
|
|
38
|
+
5,I,80,90,,,0.045,1/m,ok,
|
|
39
|
+
5,I,90,100,,,0.048,1/m,ok,
|
|
40
|
+
2,IA,16.5,16.5,30,30,16.5,m,ok,
|
|
41
|
+
2,IA,41,41,10,10,41,m,ok,
|
|
42
|
+
2,IA,66,66,3,3,66,m,ok,
|
|
43
|
+
2,IA,87,87,1,1,87,m,ok,
|
|
44
|
+
2n,IA,,,30,30,0.4,z/z(10),ok,
|
|
45
|
+
2n,IA,,,3,3,1.66,z/z(10),suspect,"does not follow from the depths in Table 2, even allowing for rounding (DATA.md section 22)"
|
|
46
|
+
2n,IA,,,1,1,2.12,z/z(10),ok,
|
|
47
|
+
3,IA,0,0,,,100,percent,ok,
|
|
48
|
+
3,IA,2,2,,,77,percent,ok,
|
|
49
|
+
3,IA,5,5,,,59,percent,ok,
|
|
50
|
+
3,IA,10,10,,,42,percent,ok,
|
|
51
|
+
3,IA,15,15,,,32,percent,ok,
|
|
52
|
+
3,IA,20,20,,,25.5,percent,ok,
|
|
53
|
+
3,IA,30,30,,,16.4,percent,ok,
|
|
54
|
+
3,IA,40,40,,,10.3,percent,ok,
|
|
55
|
+
3,IA,50,50,,,6.6,percent,ok,
|
|
56
|
+
3,IA,60,60,,,4.1,percent,ok,
|
|
57
|
+
3,IA,70,70,,,2.5,percent,ok,
|
|
58
|
+
3,IA,80,80,,,1.47,percent,ok,
|
|
59
|
+
4,IA,0,16.5,100,30,0.073,1/m,ok,
|
|
60
|
+
4,IA,16.5,41,30,10,0.043,1/m,suspect,"does not follow from the depths in Table 2, even allowing for rounding (DATA.md section 22)"
|
|
61
|
+
4,IA,41,66,10,3,0.048,1/m,ok,
|
|
62
|
+
4,IA,66,87,3,1,0.052,1/m,ok,
|
|
63
|
+
5,IA,0,2,,,0.13,1/m,ok,
|
|
64
|
+
5,IA,2,5,,,0.089,1/m,ok,
|
|
65
|
+
5,IA,5,10,,,0.068,1/m,ok,
|
|
66
|
+
5,IA,10,15,,,0.054,1/m,ok,
|
|
67
|
+
5,IA,15,20,,,0.042,1/m,ok,
|
|
68
|
+
5,IA,20,30,,,0.046,1/m,suspect,"does not follow from the percentages in Table 3, even allowing for rounding (DATA.md section 22)"
|
|
69
|
+
5,IA,30,40,,,0.046,1/m,ok,
|
|
70
|
+
5,IA,40,50,,,0.045,1/m,ok,
|
|
71
|
+
5,IA,50,60,,,0.048,1/m,ok,
|
|
72
|
+
5,IA,60,70,,,0.049,1/m,ok,
|
|
73
|
+
5,IA,70,80,,,0.053,1/m,ok,
|
|
74
|
+
2,IB,14,14,30,30,14,m,ok,
|
|
75
|
+
2,IB,33,33,10,10,33,m,ok,
|
|
76
|
+
2,IB,54,54,3,3,54,m,ok,
|
|
77
|
+
2,IB,70,70,1,1,70,m,ok,
|
|
78
|
+
2n,IB,,,30,30,0.42,z/z(10),ok,
|
|
79
|
+
2n,IB,,,3,3,1.63,z/z(10),ok,
|
|
80
|
+
2n,IB,,,1,1,2.12,z/z(10),ok,
|
|
81
|
+
3,IB,0,0,,,100,percent,ok,
|
|
82
|
+
3,IB,2,2,,,75,percent,ok,
|
|
83
|
+
3,IB,5,5,,,54,percent,ok,
|
|
84
|
+
3,IB,10,10,,,38,percent,ok,
|
|
85
|
+
3,IB,15,15,,,28,percent,ok,
|
|
86
|
+
3,IB,20,20,,,21,percent,ok,
|
|
87
|
+
3,IB,30,30,,,12.0,percent,ok,
|
|
88
|
+
3,IB,40,40,,,6.7,percent,ok,
|
|
89
|
+
3,IB,50,50,,,3.8,percent,ok,
|
|
90
|
+
3,IB,60,60,,,2.0,percent,ok,
|
|
91
|
+
3,IB,70,70,,,1.01,percent,ok,
|
|
92
|
+
4,IB,0,14,100,30,0.086,1/m,ok,
|
|
93
|
+
4,IB,14,33,30,10,0.053,1/m,suspect,"does not follow from the depths in Table 2, even allowing for rounding (DATA.md section 22)"
|
|
94
|
+
4,IB,33,54,10,3,0.057,1/m,ok,
|
|
95
|
+
4,IB,54,70,3,1,0.068,1/m,ok,
|
|
96
|
+
5,IB,0,2,,,0.144,1/m,ok,
|
|
97
|
+
5,IB,2,5,,,0.107,1/m,ok,
|
|
98
|
+
5,IB,5,10,,,0.071,1/m,ok,
|
|
99
|
+
5,IB,10,15,,,0.061,1/m,ok,
|
|
100
|
+
5,IB,15,20,,,0.056,1/m,ok,
|
|
101
|
+
5,IB,20,30,,,0.057,1/m,ok,
|
|
102
|
+
5,IB,30,40,,,0.058,1/m,ok,
|
|
103
|
+
5,IB,40,50,,,0.059,1/m,ok,
|
|
104
|
+
5,IB,50,60,,,0.064,1/m,ok,
|
|
105
|
+
5,IB,60,70,,,0.069,1/m,ok,
|
|
106
|
+
2,II,10.5,10.5,30,30,10.5,m,ok,
|
|
107
|
+
2,II,24,24,10,10,24,m,ok,
|
|
108
|
+
2,II,39,39,3,3,39,m,ok,
|
|
109
|
+
2,II,52,52,1,1,52,m,ok,
|
|
110
|
+
2n,II,,,30,30,0.44,z/z(10),ok,
|
|
111
|
+
2n,II,,,3,3,1.63,z/z(10),ok,
|
|
112
|
+
2n,II,,,1,1,2.17,z/z(10),ok,
|
|
113
|
+
3,II,0,0,,,100,percent,ok,
|
|
114
|
+
3,II,2,2,,,70,percent,ok,
|
|
115
|
+
3,II,5,5,,,48,percent,ok,
|
|
116
|
+
3,II,10,10,,,32,percent,ok,
|
|
117
|
+
3,II,15,15,,,21,percent,ok,
|
|
118
|
+
3,II,20,20,,,14.2,percent,ok,
|
|
119
|
+
3,II,30,30,,,6.4,percent,ok,
|
|
120
|
+
3,II,40,40,,,2.8,percent,ok,
|
|
121
|
+
3,II,50,50,,,1.23,percent,ok,
|
|
122
|
+
4,II,0,10.5,100,30,0.114,1/m,ok,
|
|
123
|
+
4,II,10.5,24,30,10,0.082,1/m,ok,
|
|
124
|
+
4,II,24,39,10,3,0.08,1/m,ok,
|
|
125
|
+
4,II,39,52,3,1,0.084,1/m,ok,
|
|
126
|
+
5,II,0,2,,,0.178,1/m,ok,
|
|
127
|
+
5,II,2,5,,,0.126,1/m,ok,
|
|
128
|
+
5,II,5,10,,,0.099,1/m,suspect,"does not follow from the percentages in Table 3, even allowing for rounding (DATA.md section 22)"
|
|
129
|
+
5,II,10,15,,,0.084,1/m,ok,
|
|
130
|
+
5,II,15,20,,,0.078,1/m,ok,
|
|
131
|
+
5,II,20,30,,,0.08,1/m,ok,
|
|
132
|
+
5,II,30,40,,,0.082,1/m,ok,
|
|
133
|
+
5,II,40,50,,,0.082,1/m,ok,
|
|
134
|
+
2,III,6.5,6.5,30,30,6.5,m,ok,
|
|
135
|
+
2,III,14,14,10,10,14,m,ok,
|
|
136
|
+
2,III,23,23,3,3,23,m,ok,
|
|
137
|
+
2,III,31,31,1,1,31,m,ok,
|
|
138
|
+
2n,III,,,30,30,0.46,z/z(10),ok,
|
|
139
|
+
2n,III,,,3,3,1.64,z/z(10),ok,
|
|
140
|
+
2n,III,,,1,1,2.21,z/z(10),ok,
|
|
141
|
+
3,III,0,0,,,100,percent,ok,
|
|
142
|
+
3,III,2,2,,,63,percent,ok,
|
|
143
|
+
3,III,5,5,,,38,percent,ok,
|
|
144
|
+
3,III,10,10,,,17.5,percent,ok,
|
|
145
|
+
3,III,15,15,,,8.5,percent,ok,
|
|
146
|
+
3,III,20,20,,,4.6,percent,ok,
|
|
147
|
+
3,III,30,30,,,1.22,percent,ok,
|
|
148
|
+
4,III,0,6.5,100,30,0.185,1/m,ok,
|
|
149
|
+
4,III,6.5,14,30,10,0.146,1/m,ok,
|
|
150
|
+
4,III,14,23,10,3,0.134,1/m,ok,
|
|
151
|
+
4,III,23,31,3,1,0.137,1/m,ok,
|
|
152
|
+
5,III,0,2,,,0.23,1/m,ok,
|
|
153
|
+
5,III,2,5,,,0.169,1/m,ok,
|
|
154
|
+
5,III,5,10,,,0.155,1/m,ok,
|
|
155
|
+
5,III,10,15,,,0.144,1/m,ok,
|
|
156
|
+
5,III,15,20,,,0.123,1/m,ok,
|
|
157
|
+
5,III,20,30,,,0.134,1/m,ok,
|
|
158
|
+
2,1C,5.3,5.3,30,30,5.3,m,ok,printed as coastal type '1'; written 1C here as elsewhere in this package
|
|
159
|
+
2,1C,12,12,10,10,12,m,ok,printed as coastal type '1'; written 1C here as elsewhere in this package
|
|
160
|
+
2,1C,20,20,3,3,20,m,ok,printed as coastal type '1'; written 1C here as elsewhere in this package
|
|
161
|
+
2,1C,27,27,1,1,27,m,ok,printed as coastal type '1'; written 1C here as elsewhere in this package
|
|
162
|
+
2n,1C,,,30,30,0.44,z/z(10),ok,printed as coastal type '1'; written 1C here as elsewhere in this package
|
|
163
|
+
2n,1C,,,3,3,1.67,z/z(10),ok,printed as coastal type '1'; written 1C here as elsewhere in this package
|
|
164
|
+
2n,1C,,,1,1,2.25,z/z(10),ok,printed as coastal type '1'; written 1C here as elsewhere in this package
|
|
165
|
+
3,1C,0,0,,,100,percent,ok,printed as coastal type '1'; written 1C here as elsewhere in this package
|
|
166
|
+
3,1C,2,2,,,54,percent,ok,printed as coastal type '1'; written 1C here as elsewhere in this package
|
|
167
|
+
3,1C,5,5,,,30,percent,ok,printed as coastal type '1'; written 1C here as elsewhere in this package
|
|
168
|
+
3,1C,10,10,,,13.3,percent,ok,printed as coastal type '1'; written 1C here as elsewhere in this package
|
|
169
|
+
3,1C,15,15,,,6.0,percent,ok,printed as coastal type '1'; written 1C here as elsewhere in this package
|
|
170
|
+
3,1C,20,20,,,3.0,percent,ok,printed as coastal type '1'; written 1C here as elsewhere in this package
|
|
171
|
+
3,1C,30,30,,,0.65,percent,ok,printed as coastal type '1'; written 1C here as elsewhere in this package
|
|
172
|
+
4,1C,0,5.3,100,30,0.227,1/m,ok,printed as coastal type '1'; written 1C here as elsewhere in this package
|
|
173
|
+
4,1C,5.3,12,30,10,0.164,1/m,ok,printed as coastal type '1'; written 1C here as elsewhere in this package
|
|
174
|
+
4,1C,12,20,10,3,0.15,1/m,ok,printed as coastal type '1'; written 1C here as elsewhere in this package
|
|
175
|
+
4,1C,20,27,3,1,0.157,1/m,ok,printed as coastal type '1'; written 1C here as elsewhere in this package
|
|
176
|
+
5,1C,0,2,,,0.31,1/m,ok,printed as coastal type '1'; written 1C here as elsewhere in this package
|
|
177
|
+
5,1C,2,5,,,0.196,1/m,ok,printed as coastal type '1'; written 1C here as elsewhere in this package
|
|
178
|
+
5,1C,5,10,,,0.163,1/m,ok,printed as coastal type '1'; written 1C here as elsewhere in this package
|
|
179
|
+
5,1C,10,15,,,0.154,1/m,suspect,"does not follow from the percentages in Table 3, even allowing for rounding (DATA.md section 22)"
|
|
180
|
+
5,1C,15,20,,,0.14,1/m,ok,printed as coastal type '1'; written 1C here as elsewhere in this package
|
|
181
|
+
5,1C,20,30,,,0.153,1/m,ok,printed as coastal type '1'; written 1C here as elsewhere in this package
|
|
182
|
+
2,3C,3.7,3.7,30,30,3.7,m,ok,printed as coastal type '3'; written 3C here as elsewhere in this package
|
|
183
|
+
2,3C,8.5,8.5,10,10,8.5,m,ok,printed as coastal type '3'; written 3C here as elsewhere in this package
|
|
184
|
+
2,3C,14,14,3,3,14,m,ok,printed as coastal type '3'; written 3C here as elsewhere in this package
|
|
185
|
+
2,3C,19,19,1,1,19,m,ok,printed as coastal type '3'; written 3C here as elsewhere in this package
|
|
186
|
+
2n,3C,,,30,30,0.44,z/z(10),ok,printed as coastal type '3'; written 3C here as elsewhere in this package
|
|
187
|
+
2n,3C,,,3,3,1.65,z/z(10),ok,printed as coastal type '3'; written 3C here as elsewhere in this package
|
|
188
|
+
2n,3C,,,1,1,2.23,z/z(10),ok,printed as coastal type '3'; written 3C here as elsewhere in this package
|
|
189
|
+
3,3C,0,0,,,100,percent,ok,printed as coastal type '3'; written 3C here as elsewhere in this package
|
|
190
|
+
3,3C,2,2,,,44,percent,ok,printed as coastal type '3'; written 3C here as elsewhere in this package
|
|
191
|
+
3,3C,5,5,,,21,percent,ok,printed as coastal type '3'; written 3C here as elsewhere in this package
|
|
192
|
+
3,3C,10,10,,,7.0,percent,ok,printed as coastal type '3'; written 3C here as elsewhere in this package
|
|
193
|
+
3,3C,15,15,,,2.3,percent,ok,printed as coastal type '3'; written 3C here as elsewhere in this package
|
|
194
|
+
3,3C,20,20,,,0.73,percent,ok,printed as coastal type '3'; written 3C here as elsewhere in this package
|
|
195
|
+
4,3C,0,3.7,100,30,0.325,1/m,ok,printed as coastal type '3'; written 3C here as elsewhere in this package
|
|
196
|
+
4,3C,3.7,8.5,30,10,0.229,1/m,ok,printed as coastal type '3'; written 3C here as elsewhere in this package
|
|
197
|
+
4,3C,8.5,14,10,3,0.218,1/m,ok,printed as coastal type '3'; written 3C here as elsewhere in this package
|
|
198
|
+
4,3C,14,19,3,1,0.22,1/m,ok,printed as coastal type '3'; written 3C here as elsewhere in this package
|
|
199
|
+
5,3C,0,2,,,0.41,1/m,ok,printed as coastal type '3'; written 3C here as elsewhere in this package
|
|
200
|
+
5,3C,2,5,,,0.25,1/m,ok,printed as coastal type '3'; written 3C here as elsewhere in this package
|
|
201
|
+
5,3C,5,10,,,0.22,1/m,ok,printed as coastal type '3'; written 3C here as elsewhere in this package
|
|
202
|
+
5,3C,10,15,,,0.22,1/m,ok,printed as coastal type '3'; written 3C here as elsewhere in this package
|
|
203
|
+
5,3C,15,20,,,0.23,1/m,ok,printed as coastal type '3'; written 3C here as elsewhere in this package
|
|
@@ -27,10 +27,19 @@ Three things are stated rather than hidden:
|
|
|
27
27
|
|
|
28
28
|
from __future__ import annotations
|
|
29
29
|
|
|
30
|
+
import warnings
|
|
31
|
+
from dataclasses import dataclass
|
|
32
|
+
|
|
30
33
|
import numpy as np
|
|
31
34
|
|
|
32
35
|
from . import _data
|
|
33
|
-
from .water import
|
|
36
|
+
from .water import (
|
|
37
|
+
Descent,
|
|
38
|
+
MissingQuantityError,
|
|
39
|
+
ProvenanceWarning,
|
|
40
|
+
Water,
|
|
41
|
+
_require_number,
|
|
42
|
+
)
|
|
34
43
|
|
|
35
44
|
#: Planck constant (J s), speed of light (m/s) and Avogadro constant (1/mol),
|
|
36
45
|
#: exact by the 2019 definition of the SI.
|
|
@@ -241,18 +250,20 @@ def par_profile(surface_downwelling, wavelengths, depths_m, *, kd,
|
|
|
241
250
|
f"the descent stops at {deepest:g} m, above the deepest depth "
|
|
242
251
|
f"asked for ({depths.max():g} m)"
|
|
243
252
|
)
|
|
244
|
-
|
|
245
|
-
|
|
253
|
+
if len(kd._kd) != len(kd.layers):
|
|
254
|
+
raise MissingQuantityError(
|
|
255
|
+
"this Descent was not made by descend(), so it does not "
|
|
256
|
+
"carry the Kd of its layers"
|
|
257
|
+
)
|
|
246
258
|
kd_from = (f"a descent from Jerlov {kd.surface_water_type} "
|
|
247
259
|
f"({kd.source})")
|
|
260
|
+
descent = kd
|
|
248
261
|
|
|
249
262
|
def optical_depth(z):
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
total += values * min(max(z - top, 0.0), bottom - top)
|
|
253
|
-
return total
|
|
263
|
+
# The descent's own layers, on the samples PAR rests on only.
|
|
264
|
+
return descent._optical_depth(z, near)
|
|
254
265
|
|
|
255
|
-
kd_values = (np.vstack([v for
|
|
266
|
+
kd_values = (np.vstack([v[near] for v in kd._kd]) if kd._kd
|
|
256
267
|
else np.zeros_like(sub_wl))
|
|
257
268
|
else:
|
|
258
269
|
if isinstance(kd, Water):
|
|
@@ -291,3 +302,111 @@ def par_profile(surface_downwelling, wavelengths, depths_m, *, kd,
|
|
|
291
302
|
return np.interp(band, sub_wl, optical_depth(z))
|
|
292
303
|
|
|
293
304
|
return ParProfile(depths, band, surface_q, tau, deepest, kd_from)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
QUANTA_BAND_NM = (350.0, 700.0)
|
|
308
|
+
"""The waveband of Jerlov's quanta meters, nm. Not that of PAR."""
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
@dataclass(frozen=True, eq=False, repr=False)
|
|
312
|
+
class QuantaLevels:
|
|
313
|
+
"""Jerlov's measured profiles of quanta irradiance for one water type.
|
|
314
|
+
|
|
315
|
+
Built by :func:`jerlov1977_quanta`. Every depth is in m, every
|
|
316
|
+
coefficient in 1/m, and the band is 350-700 nm, :data:`QUANTA_BAND_NM`,
|
|
317
|
+
not the 400-700 nm of :func:`par_profile`.
|
|
318
|
+
"""
|
|
319
|
+
|
|
320
|
+
water_type: str
|
|
321
|
+
level_percent: tuple[float, ...]
|
|
322
|
+
"""30, 10, 3 and 1: the levels of Table 2."""
|
|
323
|
+
level_depth_m: np.ndarray
|
|
324
|
+
"""The depth of each level, Table 2."""
|
|
325
|
+
profile_depth_m: np.ndarray
|
|
326
|
+
"""Table 3's depths, as far as the table goes for this type."""
|
|
327
|
+
profile_percent: np.ndarray
|
|
328
|
+
"""Percent of surface quanta at :attr:`profile_depth_m`, Table 3."""
|
|
329
|
+
layer_m: tuple[tuple[float, float], ...]
|
|
330
|
+
"""``(top, bottom)`` of each layer of Table 5."""
|
|
331
|
+
layer_kd: np.ndarray
|
|
332
|
+
"""Kd for quanta in each layer, Table 5."""
|
|
333
|
+
suspect: tuple[str, ...]
|
|
334
|
+
"""The layers of :attr:`layer_kd` whose printed Kd does not follow from
|
|
335
|
+
Table 3, even allowing for rounding; DATA.md section 22."""
|
|
336
|
+
|
|
337
|
+
def __repr__(self) -> str:
|
|
338
|
+
levels = ", ".join(f"{p:g}% {z:g} m" for p, z in
|
|
339
|
+
zip(self.level_percent, self.level_depth_m))
|
|
340
|
+
flag = f"; {len(self.suspect)} suspect" if self.suspect else ""
|
|
341
|
+
return f"<QuantaLevels {self.water_type}: {levels}{flag}>"
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def jerlov1977_quanta(water_type: str) -> QuantaLevels:
|
|
345
|
+
"""Jerlov's measured quanta irradiance, 350-700 nm, by water type.
|
|
346
|
+
|
|
347
|
+
From profiles measured with quanta meters in fourteen regions, Jerlov
|
|
348
|
+
tabulated the depth at which 30, 10, 3 and 1 percent of the surface
|
|
349
|
+
quanta remain (Table 2), the percentage at depth (Table 3) and Kd for
|
|
350
|
+
quanta layer by layer (Table 5), for types I, IA, IB, II, III, 1C and 3C.
|
|
351
|
+
They are measurements, so they are what to compare :func:`par_profile`
|
|
352
|
+
against; DATA.md section 22 does, and finds agreement within 2 m at
|
|
353
|
+
every level from II to 3C, and a 1 percent depth 33 percent too deep
|
|
354
|
+
for IB.
|
|
355
|
+
|
|
356
|
+
Jerlov, N. G., "Classification of sea water in terms of quanta
|
|
357
|
+
irradiance", J. Cons. int. Explor. Mer 37(3), 281-287.
|
|
358
|
+
|
|
359
|
+
Notes
|
|
360
|
+
-----
|
|
361
|
+
The band is 350-700 nm, wider than PAR's. The profiles are of real
|
|
362
|
+
water at real stations, so they include whatever changed with depth
|
|
363
|
+
there; Jerlov says so himself (p. 286). Three of the Kd values in Table
|
|
364
|
+
5 do not follow from Table 3, even allowing for rounding: for IA, II
|
|
365
|
+
and 1C, one layer each. They are listed in :attr:`QuantaLevels.suspect`,
|
|
366
|
+
and asking for those types warns. Table 2's normalised column and Table
|
|
367
|
+
4, which have five more such values, are in the shipped table
|
|
368
|
+
``jerlov1977_quanta.csv`` but not returned here, since both follow from
|
|
369
|
+
the depths that are.
|
|
370
|
+
"""
|
|
371
|
+
rows = [r for r in _data._rows("jerlov1977_quanta.csv")
|
|
372
|
+
if r["water_type"] == water_type]
|
|
373
|
+
if not rows:
|
|
374
|
+
known = []
|
|
375
|
+
for r in _data._rows("jerlov1977_quanta.csv"):
|
|
376
|
+
if r["water_type"] not in known:
|
|
377
|
+
known.append(r["water_type"])
|
|
378
|
+
raise KeyError(f"Jerlov's quanta tables have no water type "
|
|
379
|
+
f"{water_type!r} (known: {', '.join(known)})")
|
|
380
|
+
|
|
381
|
+
def table(name):
|
|
382
|
+
return [r for r in rows if r["table"] == name]
|
|
383
|
+
|
|
384
|
+
layers = table("5")
|
|
385
|
+
suspect = tuple(
|
|
386
|
+
f"Kd {r['value']} 1/m at {r['depth_top_m']}-{r['depth_bottom_m']} m"
|
|
387
|
+
for r in layers if r["status"] in _data.QUESTIONABLE
|
|
388
|
+
)
|
|
389
|
+
if suspect:
|
|
390
|
+
warnings.warn(
|
|
391
|
+
f"Jerlov's Table 5 for {water_type} prints a Kd that does not "
|
|
392
|
+
f"follow from his Table 3: " + "; ".join(suspect)
|
|
393
|
+
+ ". See DATA.md section 22.",
|
|
394
|
+
ProvenanceWarning,
|
|
395
|
+
stacklevel=_data.caller_stacklevel(),
|
|
396
|
+
)
|
|
397
|
+
levels = table("2")
|
|
398
|
+
profile = table("3")
|
|
399
|
+
return QuantaLevels(
|
|
400
|
+
water_type=water_type,
|
|
401
|
+
level_percent=tuple(float(r["percent_top"]) for r in levels),
|
|
402
|
+
level_depth_m=_data._frozen(np.array([float(r["value"])
|
|
403
|
+
for r in levels])),
|
|
404
|
+
profile_depth_m=_data._frozen(np.array([float(r["depth_top_m"])
|
|
405
|
+
for r in profile])),
|
|
406
|
+
profile_percent=_data._frozen(np.array([float(r["value"])
|
|
407
|
+
for r in profile])),
|
|
408
|
+
layer_m=tuple((float(r["depth_top_m"]), float(r["depth_bottom_m"]))
|
|
409
|
+
for r in layers),
|
|
410
|
+
layer_kd=_data._frozen(np.array([float(r["value"]) for r in layers])),
|
|
411
|
+
suspect=suspect,
|
|
412
|
+
)
|
|
@@ -9,7 +9,7 @@ same path.
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
11
|
import warnings
|
|
12
|
-
from dataclasses import dataclass
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
13
|
|
|
14
14
|
import numpy as np
|
|
15
15
|
|
|
@@ -579,8 +579,8 @@ class KdClassification:
|
|
|
579
579
|
)
|
|
580
580
|
|
|
581
581
|
|
|
582
|
-
def classify_kd(wavelengths, kd, *, source: str = "jerlov1976"
|
|
583
|
-
) -> KdClassification:
|
|
582
|
+
def classify_kd(wavelengths, kd, *, source: str = "jerlov1976",
|
|
583
|
+
types=None) -> KdClassification:
|
|
584
584
|
"""Find the Jerlov type whose Kd spectrum a measured one is closest to.
|
|
585
585
|
|
|
586
586
|
Every type of ``source`` is compared with the measurement at the given
|
|
@@ -600,6 +600,12 @@ def classify_kd(wavelengths, kd, *, source: str = "jerlov1976"
|
|
|
600
600
|
source:
|
|
601
601
|
A source carrying Kd: ``"jerlov1976"`` (the default, all ten types),
|
|
602
602
|
``"jerlov1968"``, ``"austin1986"`` or ``"solonenko2015"``.
|
|
603
|
+
types:
|
|
604
|
+
The types to compare with, if not all of ``source``'s; the oceanic
|
|
605
|
+
ones, say, for a measurement made offshore. A wavelength is left
|
|
606
|
+
out wherever any compared type has no Kd, so leaving out types with
|
|
607
|
+
gaps keeps more wavelengths: Solonenko & Mobley's 3C and 5C have
|
|
608
|
+
none from 600 nm on.
|
|
603
609
|
|
|
604
610
|
Notes
|
|
605
611
|
-----
|
|
@@ -625,7 +631,24 @@ def classify_kd(wavelengths, kd, *, source: str = "jerlov1976"
|
|
|
625
631
|
# were a gap, leaving the other wavelengths to decide alone.
|
|
626
632
|
raise ValueError("Kd must be positive and finite (NaN marks a gap)")
|
|
627
633
|
|
|
628
|
-
|
|
634
|
+
if types is None:
|
|
635
|
+
compared = src.water_types
|
|
636
|
+
else:
|
|
637
|
+
# One type given as a string is one type: tuple("III") would be
|
|
638
|
+
# three of type "I".
|
|
639
|
+
compared = (types,) if isinstance(types, str) else tuple(types)
|
|
640
|
+
unknown = [t for t in compared if t not in src.water_types]
|
|
641
|
+
if unknown:
|
|
642
|
+
raise KeyError(
|
|
643
|
+
f"source {source!r} does not cover Jerlov "
|
|
644
|
+
f"{', '.join(map(repr, unknown))} "
|
|
645
|
+
f"(available: {', '.join(src.water_types)})"
|
|
646
|
+
)
|
|
647
|
+
if not compared:
|
|
648
|
+
raise ValueError("types must name at least one water type")
|
|
649
|
+
if len(set(compared)) != len(compared):
|
|
650
|
+
raise ValueError("types names a water type more than once")
|
|
651
|
+
waters = {t: water(t, source) for t in compared}
|
|
629
652
|
# Raises for a wavelength outside the source's range. The flags are
|
|
630
653
|
# checked below, once, on the wavelengths actually compared: ten
|
|
631
654
|
# warnings for one comparison would bury the one that matters.
|
|
@@ -637,7 +660,7 @@ def classify_kd(wavelengths, kd, *, source: str = "jerlov1976"
|
|
|
637
660
|
if not usable.any():
|
|
638
661
|
raise MissingQuantityError(
|
|
639
662
|
"no wavelength has both a measured Kd and a Kd for every type "
|
|
640
|
-
f"
|
|
663
|
+
f"compared from {source!r}"
|
|
641
664
|
)
|
|
642
665
|
by_flags: dict[str, list[str]] = {}
|
|
643
666
|
for water_type, w in waters.items():
|
|
@@ -699,6 +722,58 @@ class Descent:
|
|
|
699
722
|
last stops at ``depth_m``."""
|
|
700
723
|
transmittance: np.ndarray
|
|
701
724
|
"""``Ed(depth) / Ed(0)``: ``exp(-sum(Kd_i * thickness_i))``."""
|
|
725
|
+
_kd: tuple[np.ndarray, ...] = field(default=(), repr=False)
|
|
726
|
+
"""The Kd of each layer in :attr:`layers`, on :attr:`wavelengths`."""
|
|
727
|
+
|
|
728
|
+
def _optical_depth(self, depth: float, columns=slice(None)) -> np.ndarray:
|
|
729
|
+
"""``sum(Kd_i * thickness_i)`` from the surface to ``depth``.
|
|
730
|
+
|
|
731
|
+
``columns`` picks wavelengths, so that a caller using only some of
|
|
732
|
+
them, as PAR does, never touches the rest.
|
|
733
|
+
"""
|
|
734
|
+
total = np.zeros(np.size(self.wavelengths))[columns]
|
|
735
|
+
for (top, bottom, _), values in zip(self.layers, self._kd):
|
|
736
|
+
total = total + values[columns] * min(max(depth - top, 0.0),
|
|
737
|
+
bottom - top)
|
|
738
|
+
return total
|
|
739
|
+
|
|
740
|
+
def transmittance_at(self, depth_m):
|
|
741
|
+
"""``Ed(z) / Ed(0)`` at any depth from the surface to :attr:`depth_m`.
|
|
742
|
+
|
|
743
|
+
Uses the layers already crossed, so it is what :func:`descend` to
|
|
744
|
+
that depth would give, without calling it again. A single depth
|
|
745
|
+
gives an array on :attr:`wavelengths` (a float if the descent was
|
|
746
|
+
asked for at one wavelength); an array of depths gives one row per
|
|
747
|
+
depth.
|
|
748
|
+
|
|
749
|
+
Raises
|
|
750
|
+
------
|
|
751
|
+
MissingQuantityError
|
|
752
|
+
Below :attr:`depth_m`. Descend further, if the profile allows:
|
|
753
|
+
see :func:`profile_depth`.
|
|
754
|
+
"""
|
|
755
|
+
if len(self._kd) != len(self.layers):
|
|
756
|
+
# Only descend() records the Kd of each layer; without it every
|
|
757
|
+
# depth would come back as 1, looking like perfectly clear water.
|
|
758
|
+
raise MissingQuantityError(
|
|
759
|
+
"this Descent was not made by descend(), so it does not "
|
|
760
|
+
"carry the Kd of its layers"
|
|
761
|
+
)
|
|
762
|
+
depths = np.asarray(depth_m, dtype=float)
|
|
763
|
+
if not np.all(np.isfinite(depths)):
|
|
764
|
+
raise ValueError(f"depth_m must be finite numbers, not {depth_m!r}")
|
|
765
|
+
if np.any(depths < 0):
|
|
766
|
+
raise ValueError("depth_m cannot be negative")
|
|
767
|
+
if np.any(depths > self.depth_m):
|
|
768
|
+
raise MissingQuantityError(
|
|
769
|
+
f"this descent stops at {self.depth_m:g} m, above "
|
|
770
|
+
f"{depths.max():g} m; descend further to go deeper"
|
|
771
|
+
)
|
|
772
|
+
out = np.array([np.exp(-self._optical_depth(float(z)))
|
|
773
|
+
for z in depths.ravel()])
|
|
774
|
+
if depths.ndim == 0:
|
|
775
|
+
return _like_input(out[0], self.transmittance)
|
|
776
|
+
return out.reshape(depths.shape + (np.size(self.wavelengths),))
|
|
702
777
|
|
|
703
778
|
def __repr__(self) -> str:
|
|
704
779
|
# Adjacent 10 m layers of one type are shown as one span.
|
|
@@ -808,7 +883,38 @@ def descend(surface_water_type: str, depth_m: float, wavelengths, *,
|
|
|
808
883
|
wavelengths=query,
|
|
809
884
|
layers=tuple(layers),
|
|
810
885
|
transmittance=_like_input(np.exp(-optical_depth), wavelengths),
|
|
886
|
+
_kd=tuple(_frozen_copy(kd[t]) for _, _, t in layers),
|
|
887
|
+
)
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
def _frozen_copy(values: np.ndarray) -> np.ndarray:
|
|
891
|
+
out = np.array(values, dtype=float)
|
|
892
|
+
out.setflags(write=False)
|
|
893
|
+
return out
|
|
894
|
+
|
|
895
|
+
|
|
896
|
+
def profile_depth(surface_water_type: str) -> float:
|
|
897
|
+
"""How deep :func:`descend` can go below ``surface_water_type``, in m.
|
|
898
|
+
|
|
899
|
+
The top of the first layer for which Williamson & Hollins (2023)
|
|
900
|
+
declared no type, or 200 m, where their profile ends. Coastal types run
|
|
901
|
+
out early: 9C at 10 m, 5C and 7C at 20 m, 3C at 70 m.
|
|
902
|
+
|
|
903
|
+
::
|
|
904
|
+
|
|
905
|
+
d = jerlov.descend("3C", jerlov.profile_depth("3C"), wl) # to 70 m
|
|
906
|
+
"""
|
|
907
|
+
rows = sorted(
|
|
908
|
+
(r for r in _data._rows("williamson2023_depth.csv")
|
|
909
|
+
if r["surface_water_type"] == surface_water_type),
|
|
910
|
+
key=lambda r: float(r["depth_min_m"]),
|
|
811
911
|
)
|
|
912
|
+
if not rows:
|
|
913
|
+
water_type_at_depth(surface_water_type, 0.0) # raises, naming types
|
|
914
|
+
for row in rows:
|
|
915
|
+
if not row["water_type"]:
|
|
916
|
+
return float(row["depth_min_m"])
|
|
917
|
+
return float(rows[-1]["depth_max_m"])
|
|
812
918
|
|
|
813
919
|
|
|
814
920
|
#: Why each source without a pure water absorption has none.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jerlov
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.4
|
|
4
4
|
Summary: Inherent optical properties of Jerlov water types, with provenance
|
|
5
5
|
Author: T. Ishibashi
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -285,6 +285,9 @@ scene = jerlov.Scene.at_depth(jerlov.water("II"), 45.0, surface, wl, kd=d)
|
|
|
285
285
|
|
|
286
286
|
It refuses to go through a layer the paper declared nothing for, rather than
|
|
287
287
|
carrying the last type on: 3C stops at 70 m, 9C at 10 m.
|
|
288
|
+
`jerlov.profile_depth("3C")` says how far a descent can go, and
|
|
289
|
+
`d.transmittance_at(z)` gives any depth above the descent's bottom without
|
|
290
|
+
descending again.
|
|
288
291
|
|
|
289
292
|
## Light for photosynthesis
|
|
290
293
|
|
|
@@ -304,6 +307,18 @@ default, because PAR counts photons and an energy spectrum read as photons is
|
|
|
304
307
|
weighted wrongly with no sign of it. This is planar PAR, from Ed; PAR on
|
|
305
308
|
scalar irradiance is larger by a factor this package cannot supply.
|
|
306
309
|
|
|
310
|
+
Jerlov measured quanta irradiance, 350-700 nm, by water type, and his
|
|
311
|
+
tables are shipped for checking against:
|
|
312
|
+
|
|
313
|
+
```python
|
|
314
|
+
jerlov.jerlov1977_quanta("II") # <QuantaLevels II: 30% 10.5 m, 10% 24 m, 3% 39 m, 1% 52 m; ...>
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
From II to 3C the package agrees with them within 2 m at every level. For
|
|
318
|
+
Jerlov I it agrees only through `descend`; for IB it puts the 1 percent
|
|
319
|
+
level a third too deep, because Jerlov's IB water grew more turbid below
|
|
320
|
+
15 m. DATA.md section 22.
|
|
321
|
+
|
|
307
322
|
## Which type is my water nearest?
|
|
308
323
|
|
|
309
324
|
```python
|
|
@@ -315,7 +330,9 @@ r.beyond # "clearer" or "more turbid" than every type, or None
|
|
|
315
330
|
|
|
316
331
|
It ranks the types of one edition; it draws no boundaries, since those differ
|
|
317
332
|
between editions. A single wavelength is accepted, and ranks oceanic and
|
|
318
|
-
coastal types less well apart than a spectrum does.
|
|
333
|
+
coastal types less well apart than a spectrum does. `types=` compares with
|
|
334
|
+
some of the types only, which keeps the wavelengths where only the others
|
|
335
|
+
have gaps.
|
|
319
336
|
|
|
320
337
|
## Other entry points
|
|
321
338
|
|
|
@@ -389,8 +406,8 @@ made.
|
|
|
389
406
|
## Provenance and design
|
|
390
407
|
|
|
391
408
|
`DATA.md` records, for every shipped table, where it came from, what was
|
|
392
|
-
verified, and what is known to be wrong with it. Twenty-
|
|
393
|
-
documented there:
|
|
409
|
+
verified, and what is known to be wrong with it. Twenty-two entries are
|
|
410
|
+
documented there: nine confirmed defects in the source literature, three
|
|
394
411
|
questions the first edition of Jerlov settled, and the rest notes.
|
|
395
412
|
|
|
396
413
|
`DECISIONS.md` records why the package is shaped the way it is, including the
|
|
@@ -27,6 +27,7 @@ jerlov/data/cie_d65.csv
|
|
|
27
27
|
jerlov/data/jerlov1968_kd.csv
|
|
28
28
|
jerlov/data/jerlov1968_total_irradiance.csv
|
|
29
29
|
jerlov/data/jerlov1976_kd.csv
|
|
30
|
+
jerlov/data/jerlov1977_quanta.csv
|
|
30
31
|
jerlov/data/paulson1977_shortwave.csv
|
|
31
32
|
jerlov/data/petzold1972_stations.csv
|
|
32
33
|
jerlov/data/petzold1972_vsf.csv
|
|
@@ -44,6 +45,7 @@ tests/test_depth.py
|
|
|
44
45
|
tests/test_packaging.py
|
|
45
46
|
tests/test_par.py
|
|
46
47
|
tests/test_petzold.py
|
|
48
|
+
tests/test_quanta.py
|
|
47
49
|
tests/test_quoted_figures.py
|
|
48
50
|
tests/test_reproduces_papers.py
|
|
49
51
|
tests/test_scene.py
|
|
@@ -89,3 +89,33 @@ def test_what_cannot_be_compared_is_refused():
|
|
|
89
89
|
jerlov.classify_kd(WL, [0.05])
|
|
90
90
|
with pytest.raises(jerlov.MissingQuantityError, match="no wavelength"):
|
|
91
91
|
jerlov.classify_kd([500.0], [np.nan])
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_types_narrows_the_comparison_and_keeps_more_wavelengths():
|
|
95
|
+
"""Solonenko & Mobley's 3C and 5C have no Kd from 600 nm on, which
|
|
96
|
+
took those wavelengths out of every comparison."""
|
|
97
|
+
wl = np.arange(400.0, 701.0, 25.0)
|
|
98
|
+
oceanic = ("I", "IA", "IB", "II", "III")
|
|
99
|
+
with warnings.catch_warnings():
|
|
100
|
+
warnings.simplefilter("ignore", jerlov.ProvenanceWarning)
|
|
101
|
+
kd = jerlov.water("II", source="solonenko2015").kd(wl)
|
|
102
|
+
every = jerlov.classify_kd(wl, kd, source="solonenko2015")
|
|
103
|
+
some = jerlov.classify_kd(wl, kd, source="solonenko2015",
|
|
104
|
+
types=oceanic)
|
|
105
|
+
assert every.excluded_nm == (600.0, 625.0, 650.0, 675.0, 700.0)
|
|
106
|
+
assert some.excluded_nm == ()
|
|
107
|
+
assert some.water_type == "II"
|
|
108
|
+
assert {t for t, _ in some.distances} == set(oceanic)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def test_types_must_be_types_of_the_source():
|
|
112
|
+
with pytest.raises(KeyError, match="does not cover Jerlov '3C'"):
|
|
113
|
+
jerlov.classify_kd(WL, _kd("II"), source="austin1986",
|
|
114
|
+
types=("II", "IA", "3C"))
|
|
115
|
+
with pytest.raises(ValueError, match="at least one"):
|
|
116
|
+
jerlov.classify_kd(WL, _kd("II"), types=())
|
|
117
|
+
with pytest.raises(ValueError, match="more than once"):
|
|
118
|
+
jerlov.classify_kd(WL, _kd("II"), types=("II", "II"))
|
|
119
|
+
# A string is one type, not a sequence of letters.
|
|
120
|
+
alone = jerlov.classify_kd(WL, _kd("III"), types="III")
|
|
121
|
+
assert alone.distances == (("III", 0.0),)
|
|
@@ -190,3 +190,50 @@ def test_a_descent_prints_its_path_not_its_arrays():
|
|
|
190
190
|
"Kd from jerlov1976; 301 wavelengths>")
|
|
191
191
|
assert repr(jerlov.descend("II", 0.0, 500.0)).startswith(
|
|
192
192
|
"<Descent II to 0 m: no layer crossed;")
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def test_profile_depth_is_where_descend_stops():
|
|
196
|
+
"""Three examples searched for this by trying descend() every 10 m."""
|
|
197
|
+
expected = {"I": 200.0, "IB": 200.0, "III": 170.0, "1C": 110.0,
|
|
198
|
+
"3C": 70.0, "5C": 20.0, "7C": 20.0, "9C": 10.0}
|
|
199
|
+
for water_type, depth in expected.items():
|
|
200
|
+
assert jerlov.profile_depth(water_type) == depth
|
|
201
|
+
jerlov.descend(water_type, depth, 500.0) # reaches it
|
|
202
|
+
with pytest.raises(jerlov.MissingQuantityError):
|
|
203
|
+
jerlov.descend(water_type, depth + 0.1, 500.0)
|
|
204
|
+
with pytest.raises(KeyError, match="known"):
|
|
205
|
+
jerlov.profile_depth("XI")
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def test_transmittance_at_is_a_shorter_descent():
|
|
209
|
+
wl = np.arange(400.0, 701.0, 10.0)
|
|
210
|
+
d = jerlov.descend("1C", 45.0, wl)
|
|
211
|
+
for z in (0.0, 7.5, 10.0, 20.0, 33.3, 45.0):
|
|
212
|
+
assert d.transmittance_at(z) == pytest.approx(
|
|
213
|
+
jerlov.descend("1C", z, wl).transmittance, rel=1e-12)
|
|
214
|
+
rows = d.transmittance_at([[0.0, 10.0], [20.0, 45.0]])
|
|
215
|
+
assert rows.shape == (2, 2, wl.size)
|
|
216
|
+
assert rows[1, 1] == pytest.approx(d.transmittance)
|
|
217
|
+
one = jerlov.descend("II", 30.0, 500.0)
|
|
218
|
+
assert isinstance(one.transmittance_at(12.0), float)
|
|
219
|
+
assert one.transmittance_at(12.0) == jerlov.descend("II", 12.0, 500.0).transmittance
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def test_transmittance_at_refuses_what_the_descent_does_not_cover():
|
|
223
|
+
d = jerlov.descend("I", 30.0, [450.0, 550.0])
|
|
224
|
+
with pytest.raises(jerlov.MissingQuantityError, match="stops at 30 m"):
|
|
225
|
+
d.transmittance_at(31.0)
|
|
226
|
+
for bad in (-1.0, float("nan"), float("inf")):
|
|
227
|
+
with pytest.raises(ValueError):
|
|
228
|
+
d.transmittance_at(bad)
|
|
229
|
+
# A Descent built by hand has no layer Kd; it must not look clear.
|
|
230
|
+
hand = jerlov.Descent("I", 30.0, "jerlov1976", d.wavelengths, d.layers,
|
|
231
|
+
d.transmittance)
|
|
232
|
+
with pytest.raises(jerlov.MissingQuantityError, match="descend"):
|
|
233
|
+
hand.transmittance_at(10.0)
|
|
234
|
+
with pytest.raises(jerlov.MissingQuantityError, match="descend"):
|
|
235
|
+
jerlov.par_profile(np.ones(301), np.arange(400.0, 701.0), 0.0,
|
|
236
|
+
kd=jerlov.Descent("I", 30.0, "jerlov1976",
|
|
237
|
+
np.arange(400.0, 701.0),
|
|
238
|
+
d.layers, np.ones(301)),
|
|
239
|
+
unit="photons")
|
|
@@ -389,3 +389,29 @@ def test_the_READMEs_count_the_examples():
|
|
|
389
389
|
for script in (ROOT / "examples").glob("*.py"):
|
|
390
390
|
assert script.name in readme.read_text(), (
|
|
391
391
|
f"{readme.relative_to(ROOT)} never mentions {script.name}")
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
@source_tree
|
|
395
|
+
def test_the_tests_read_the_installed_tables_not_the_source_tree():
|
|
396
|
+
"""The suite is also run against the wheel, where there is no
|
|
397
|
+
jerlov/data/ beside the tests. test_petzold.py read it from there, and
|
|
398
|
+
the first run against a wheel failed."""
|
|
399
|
+
offenders = []
|
|
400
|
+
for path in (ROOT / "tests").glob("test_*.py"):
|
|
401
|
+
if path.name == "test_packaging.py":
|
|
402
|
+
continue
|
|
403
|
+
if re.search(r'"jerlov"\s*/\s*"data"', path.read_text()):
|
|
404
|
+
offenders.append(path.name)
|
|
405
|
+
assert not offenders, (
|
|
406
|
+
f"{offenders} read tables from the source tree; use "
|
|
407
|
+
"importlib.resources.files('jerlov.data')"
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
@source_tree
|
|
412
|
+
def test_publishing_checks_the_tag_and_runs_the_suite_on_the_wheel():
|
|
413
|
+
workflow = (ROOT / ".github" / "workflows" / "publish.yml").read_text()
|
|
414
|
+
assert "GITHUB_REF_NAME" in workflow and "pyproject.toml" in workflow, (
|
|
415
|
+
"publish.yml no longer compares the tag with the version")
|
|
416
|
+
assert "pytest wheel-tests" in workflow, (
|
|
417
|
+
"publish.yml no longer runs the tests against the installed wheel")
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import csv
|
|
6
6
|
import re
|
|
7
|
+
from importlib import resources
|
|
7
8
|
import warnings
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
|
|
@@ -18,7 +19,9 @@ ROOT = Path(__file__).resolve().parent.parent
|
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
def _table(station):
|
|
21
|
-
|
|
22
|
+
# The installed package's own copy, not the source tree's: the suite is
|
|
23
|
+
# also run against the wheel, where there is no source tree to read.
|
|
24
|
+
path = resources.files("jerlov.data").joinpath("petzold1972_vsf.csv")
|
|
22
25
|
with path.open(encoding="utf-8") as handle:
|
|
23
26
|
return [r for r in csv.DictReader(handle) if r["station"] == station]
|
|
24
27
|
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""Jerlov's measured quanta irradiance, and the package checked against it.
|
|
2
|
+
|
|
3
|
+
The tables are transcribed in tools/build_jerlov1977.py, which also checks
|
|
4
|
+
them against each other. Here: that they are read back as printed, and the
|
|
5
|
+
comparison DATA.md section 22 makes between them and what `descend` and
|
|
6
|
+
Jerlov's (1976) Kd predict.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import math
|
|
12
|
+
import warnings
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
import pytest
|
|
16
|
+
|
|
17
|
+
import jerlov
|
|
18
|
+
from jerlov._data import trapezoid
|
|
19
|
+
|
|
20
|
+
TYPES = ("I", "IA", "IB", "II", "III", "1C", "3C")
|
|
21
|
+
BAND = np.arange(350.0, 701.0, 5.0) # Jerlov's quanta band
|
|
22
|
+
PHOTONS = np.interp(BAND, *jerlov.d65()) * BAND
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _quanta(water_type):
|
|
26
|
+
with warnings.catch_warnings():
|
|
27
|
+
warnings.simplefilter("ignore", jerlov.ProvenanceWarning)
|
|
28
|
+
return jerlov.jerlov1977_quanta(water_type)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _level_depths(transmittance_at, deepest):
|
|
32
|
+
"""Depths of 30, 10, 3 and 1 percent of surface quanta, 350-700 nm."""
|
|
33
|
+
surface = trapezoid(PHOTONS, BAND)
|
|
34
|
+
|
|
35
|
+
def fraction(z):
|
|
36
|
+
return trapezoid(PHOTONS * transmittance_at(z), BAND) / surface
|
|
37
|
+
|
|
38
|
+
out = []
|
|
39
|
+
for level in (0.30, 0.10, 0.03, 0.01):
|
|
40
|
+
low, high = 0.0, deepest
|
|
41
|
+
if fraction(high) > level:
|
|
42
|
+
out.append(None)
|
|
43
|
+
continue
|
|
44
|
+
for _ in range(60):
|
|
45
|
+
middle = 0.5 * (low + high)
|
|
46
|
+
if fraction(middle) > level:
|
|
47
|
+
low = middle
|
|
48
|
+
else:
|
|
49
|
+
high = middle
|
|
50
|
+
out.append(0.5 * (low + high))
|
|
51
|
+
return out
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _one_kd(water_type):
|
|
55
|
+
kd = jerlov.water(water_type, source="jerlov1976").kd(BAND)
|
|
56
|
+
return _level_depths(lambda z: np.exp(-kd * z), 1000.0)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _layered(water_type):
|
|
60
|
+
d = jerlov.descend(water_type, jerlov.profile_depth(water_type), BAND)
|
|
61
|
+
return _level_depths(d.transmittance_at, d.depth_m)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# -- the tables as printed ---------------------------------------------------
|
|
65
|
+
|
|
66
|
+
def test_table_2_is_read_back_as_printed():
|
|
67
|
+
q = _quanta("I")
|
|
68
|
+
assert q.level_percent == (30.0, 10.0, 3.0, 1.0)
|
|
69
|
+
assert q.level_depth_m.tolist() == [19, 49, 79, 103]
|
|
70
|
+
assert _quanta("3C").level_depth_m.tolist() == [3.7, 8.5, 14, 19]
|
|
71
|
+
assert repr(_quanta("II")) == (
|
|
72
|
+
"<QuantaLevels II: 30% 10.5 m, 10% 24 m, 3% 39 m, 1% 52 m; "
|
|
73
|
+
"1 suspect>")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_tables_3_and_5_go_as_deep_as_the_paper():
|
|
77
|
+
q = _quanta("I")
|
|
78
|
+
assert q.profile_depth_m[-1] == 100 and q.profile_percent[-1] == 1.14
|
|
79
|
+
assert q.layer_m[-1] == (90.0, 100.0) and q.layer_kd[-1] == 0.048
|
|
80
|
+
q = _quanta("3C")
|
|
81
|
+
assert q.profile_depth_m.tolist() == [0, 2, 5, 10, 15, 20]
|
|
82
|
+
assert len(q.layer_kd) == 5
|
|
83
|
+
for array in (q.level_depth_m, q.profile_percent, q.layer_kd):
|
|
84
|
+
with pytest.raises(ValueError):
|
|
85
|
+
array[0] = 0.0
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_the_suspect_layers_warn_and_the_others_do_not():
|
|
89
|
+
for water_type, layer in (("IA", "20.0-30.0"), ("II", "5.0-10.0"),
|
|
90
|
+
("1C", "10.0-15.0")):
|
|
91
|
+
with pytest.warns(jerlov.ProvenanceWarning, match="Table 5"):
|
|
92
|
+
q = jerlov.jerlov1977_quanta(water_type)
|
|
93
|
+
assert len(q.suspect) == 1 and layer.replace(".0", "") in \
|
|
94
|
+
q.suspect[0].replace(".0", "")
|
|
95
|
+
with warnings.catch_warnings():
|
|
96
|
+
warnings.simplefilter("error")
|
|
97
|
+
for water_type in ("I", "IB", "III", "3C"):
|
|
98
|
+
assert jerlov.jerlov1977_quanta(water_type).suspect == ()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_an_unknown_type_names_the_known_ones():
|
|
102
|
+
with pytest.raises(KeyError, match="known: I, IA, IB, II, III, 1C, 3C"):
|
|
103
|
+
jerlov.jerlov1977_quanta("5C")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# -- the package against the measurements: DATA.md section 22 ---------------
|
|
107
|
+
|
|
108
|
+
def test_from_II_to_3C_one_kd_agrees_within_2_m_at_every_level():
|
|
109
|
+
for water_type in ("II", "III", "1C", "3C"):
|
|
110
|
+
model = _one_kd(water_type)
|
|
111
|
+
printed = _quanta(water_type).level_depth_m
|
|
112
|
+
assert max(abs(m - p) for m, p in zip(model, printed)) < 2.0, \
|
|
113
|
+
water_type
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_jerlov_I_needs_the_depth_profile():
|
|
117
|
+
"""One Kd puts the 1 percent level of I at 165 m; Jerlov measured 103;
|
|
118
|
+
layer by layer it is 106."""
|
|
119
|
+
assert round(_one_kd("I")[3]) == 165
|
|
120
|
+
assert round(_layered("I")[3]) == 106
|
|
121
|
+
assert round(_layered("I")[1]) == 49 # 10 percent: 49 printed
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test_IA_and_IB_stay_too_deep():
|
|
125
|
+
"""Layer by layer IA reaches 1 percent at 100 m against 87, and IB at
|
|
126
|
+
93 m against 70: 15 and 33 percent too deep."""
|
|
127
|
+
ia, ib = _layered("IA")[3], _layered("IB")[3]
|
|
128
|
+
assert (round(ia), round(ib)) == (100, 93)
|
|
129
|
+
assert round(100 * (ia / 87 - 1)) == 15
|
|
130
|
+
assert round(100 * (ib / 70 - 1)) == 33
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_IB_agrees_in_its_own_10_m_and_departs_below():
|
|
134
|
+
"""Kd for quanta in IB, Jerlov (1976) against Table 5: within 0.004 1/m
|
|
135
|
+
above 10 m; below 15 m one falls from 0.051 to 0.041, the other rises
|
|
136
|
+
from 0.056 to 0.069."""
|
|
137
|
+
kd = jerlov.water("IB", source="jerlov1976").kd(BAND)
|
|
138
|
+
|
|
139
|
+
def quanta(z):
|
|
140
|
+
return trapezoid(PHOTONS * np.exp(-kd * z), BAND)
|
|
141
|
+
|
|
142
|
+
q = _quanta("IB")
|
|
143
|
+
model = np.array([math.log(quanta(a) / quanta(b)) / (b - a)
|
|
144
|
+
for a, b in q.layer_m])
|
|
145
|
+
top = [i for i, (a, b) in enumerate(q.layer_m) if b <= 10]
|
|
146
|
+
deep = [i for i, (a, b) in enumerate(q.layer_m) if a >= 15]
|
|
147
|
+
assert np.max(np.abs(model[top] - q.layer_kd[top])) < 0.004
|
|
148
|
+
assert (round(model[deep[0]], 3),
|
|
149
|
+
round(model[deep[-1]], 3)) == (0.051, 0.041)
|
|
150
|
+
assert (q.layer_kd[deep[0]], q.layer_kd[deep[-1]]) == (0.056, 0.069)
|
|
151
|
+
assert np.all(np.diff(model[deep]) < 0)
|
|
152
|
+
assert np.all(np.diff(q.layer_kd[deep]) >= 0)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def test_DATA_md_figures_for_the_tables_against_each_other():
|
|
156
|
+
"""The recomputed ranges in DATA.md section 22, and integrating Table 5
|
|
157
|
+
down to 10 m for II gives 29 percent against Table 3's 32."""
|
|
158
|
+
def ratio_range(z, z10):
|
|
159
|
+
r = [(z + a) / (z10 + b) for a in (-0.5, 0.5) for b in (-0.5, 0.5)]
|
|
160
|
+
return f"{min(r):.2f}-{max(r):.2f}"
|
|
161
|
+
assert ratio_range(79, 49) == "1.59-1.64"
|
|
162
|
+
assert ratio_range(103, 49) == "2.07-2.13"
|
|
163
|
+
assert ratio_range(66, 41) == "1.58-1.64"
|
|
164
|
+
q = _quanta("II")
|
|
165
|
+
tau = sum(k * (b - a) for (a, b), k in zip(q.layer_m, q.layer_kd)
|
|
166
|
+
if b <= 10)
|
|
167
|
+
assert round(100 * math.exp(-tau)) == 29
|
|
168
|
+
assert q.profile_percent[q.profile_depth_m.tolist().index(10)] == 32
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|