smartg 2.0.2__tar.gz → 2.0.3__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.
- {smartg-2.0.2/smartg.egg-info → smartg-2.0.3}/PKG-INFO +6 -1
- {smartg-2.0.2 → smartg-2.0.3}/README.md +5 -0
- {smartg-2.0.2 → smartg-2.0.3}/pyproject.toml +1 -1
- {smartg-2.0.2 → smartg-2.0.3}/smartg/smartg.py +169 -21
- {smartg-2.0.2 → smartg-2.0.3}/smartg/src/device.cu +27 -10
- smartg-2.0.3/smartg/tests/test_jax_context.py +46 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_smartg.py +173 -1
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_smartg_host.py +12 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_smartg_jax.py +28 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_view.py +20 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/view.py +7 -0
- {smartg-2.0.2 → smartg-2.0.3/smartg.egg-info}/PKG-INFO +6 -1
- {smartg-2.0.2 → smartg-2.0.3}/smartg.egg-info/SOURCES.txt +1 -0
- {smartg-2.0.2 → smartg-2.0.3}/LICENSE.TXT +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/LICENSE_RNG.TXT +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/MANIFEST.in +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/setup.cfg +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/__init__.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/albedo.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/atmosphere.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/auxdata.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/bandset.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/cdf.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/config.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/conftest.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/diff.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/environ.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/grid3d.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/histories.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/interp.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/iprt/__init__.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/iprt/common.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/iprt/phase3.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/iprt/phase_a.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/iprt/phase_b.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/kdis.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/notebooks/demo_notebook.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/notebooks/demo_notebook_objects.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/notebooks/demo_notebook_photons_histories.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/notebooks/validation_smartg_iprt_phase3.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/notebooks/validation_smartg_iprt_phase_a.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/notebooks/validation_smartg_iprt_phase_b_c2.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/objects3d.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/phase.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/postprocess.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/progress.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/reptran.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/rrs.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/sensor.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/src/communs.h +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/src/device.h +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/src/geometry.h +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/src/helper_math.h +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/src/incRNGs/Random123/MicroURNG.hpp +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/src/incRNGs/Random123/ReinterpretCtr.hpp +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/src/incRNGs/Random123/aes.h +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/src/incRNGs/Random123/array.h +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/src/incRNGs/Random123/ars.h +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/src/incRNGs/Random123/conventional/Engine.hpp +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/src/incRNGs/Random123/conventional/gsl_cbrng.h +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/src/incRNGs/Random123/features/clangfeatures.h +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/src/incRNGs/Random123/features/compilerfeatures.h +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/src/incRNGs/Random123/features/gccfeatures.h +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/src/incRNGs/Random123/features/iccfeatures.h +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/src/incRNGs/Random123/features/msvcfeatures.h +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/src/incRNGs/Random123/features/nvccfeatures.h +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/src/incRNGs/Random123/features/open64features.h +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/src/incRNGs/Random123/features/openclfeatures.h +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/src/incRNGs/Random123/features/pgccfeatures.h +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/src/incRNGs/Random123/features/sse.h +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/src/incRNGs/Random123/features/sunprofeatures.h +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/src/incRNGs/Random123/features/xlcfeatures.h +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/src/incRNGs/Random123/gsl_microrng.h +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/src/incRNGs/Random123/philox.h +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/src/incRNGs/Random123/threefry.h +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/src/kernel2.cu +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/src/kernel2.h +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/src/math_constants.h +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/src/phase_grid.h +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/src/shapes.h +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/src/transform.h +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/surface.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/__init__.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/conftest.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/iprt_checks.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/logs/.gitkeep +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_aerosol.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_atm3d.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_auxdata.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_bandset.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_cdf.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_chowdhary_aos.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_conftest.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_cuda.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_environ.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_histories.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_iprt_checks.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_iprt_phase3.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_iprt_phase_b_c2.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_iprt_phase_b_c3.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_kdis.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_kokhanovsky_natraj.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_notebook.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_objects3d.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_objects3d_geometry.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_opac.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_phase_grid.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_phase_grid_ice.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_phase_mix.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_phase_readers.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_postprocess.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_profile.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_progress.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_quick_iprt_phase_a.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_reptran.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_rrs.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_self_consistency.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_surface.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_thermal.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_truncation.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_truncation_components.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_vrs.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_water.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/tests/test_xarray.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/truncation.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/typing.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/vrs.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/water.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg/xarray.py +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg.egg-info/dependency_links.txt +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg.egg-info/requires.txt +0 -0
- {smartg-2.0.2 → smartg-2.0.3}/smartg.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: smartg
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.3
|
|
4
4
|
Summary: SMART-G (Speed-up Monte Carlo Advanced Radiative Transfer Code using GPU) is a radiative transfer code using a Monte-Carlo technique to simulate the propagation of the polarized light in the atmosphere and/or ocean, and using GPU acceleration.
|
|
5
5
|
Author-email: Mustapha Moulana <mm@hygeos.com>, Didier Ramon <dr@hygeos.com>, François Steinmetz <fs@hygeos.com>, Dominique Jolivet <dj@hygeos.com>, Mathieu Compiègne <mc@hygeos.com>
|
|
6
6
|
Project-URL: Source, https://github.com/hygeos/smartg
|
|
@@ -326,6 +326,11 @@ environment. Without them it skips itself with a message rather than
|
|
|
326
326
|
failing, so run it with `pixi run -e extra pytest
|
|
327
327
|
smartg/tests/test_smartg_jax.py`.
|
|
328
328
|
|
|
329
|
+
`test_jax_context.py` needs JAX on a GPU, in the same environment, and
|
|
330
|
+
a pytest process of its own: `test_smartg_jax.py` keeps JAX on the CPU
|
|
331
|
+
for the whole process, and the test then skips itself. Run it with
|
|
332
|
+
`pixi run -e extra pytest smartg/tests/test_jax_context.py`.
|
|
333
|
+
|
|
329
334
|
To avoid repeating some pytest arguments, a `pytest.ini` file can be created at the root of the project. The following is an example of the contents of such a file:
|
|
330
335
|
|
|
331
336
|
```
|
|
@@ -275,6 +275,11 @@ environment. Without them it skips itself with a message rather than
|
|
|
275
275
|
failing, so run it with `pixi run -e extra pytest
|
|
276
276
|
smartg/tests/test_smartg_jax.py`.
|
|
277
277
|
|
|
278
|
+
`test_jax_context.py` needs JAX on a GPU, in the same environment, and
|
|
279
|
+
a pytest process of its own: `test_smartg_jax.py` keeps JAX on the CPU
|
|
280
|
+
for the whole process, and the test then skips itself. Run it with
|
|
281
|
+
`pixi run -e extra pytest smartg/tests/test_jax_context.py`.
|
|
282
|
+
|
|
278
283
|
To avoid repeating some pytest arguments, a `pytest.ini` file can be created at the root of the project. The following is an example of the contents of such a file:
|
|
279
284
|
|
|
280
285
|
```
|
|
@@ -4,7 +4,7 @@ requires = ["setuptools>=61"]
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "smartg"
|
|
7
|
-
version = "2.0.
|
|
7
|
+
version = "2.0.3"
|
|
8
8
|
description = """
|
|
9
9
|
SMART-G (Speed-up Monte Carlo Advanced Radiative Transfer Code using GPU) is a radiative \
|
|
10
10
|
transfer code using a Monte-Carlo technique to simulate the propagation of the polarized \
|
|
@@ -32,6 +32,8 @@ reduce_diff
|
|
|
32
32
|
Post-process ALIS finite-difference runs into sensitivities.
|
|
33
33
|
"""
|
|
34
34
|
|
|
35
|
+
import atexit
|
|
36
|
+
import functools
|
|
35
37
|
import os
|
|
36
38
|
import subprocess
|
|
37
39
|
import tempfile
|
|
@@ -402,7 +404,7 @@ class Alis:
|
|
|
402
404
|
"""
|
|
403
405
|
The options of the ALIS method, for Smartg.run.
|
|
404
406
|
|
|
405
|
-
ALIS (Emde et al.
|
|
407
|
+
ALIS (Emde et al. 2011) computes the spectrum of a whole band from
|
|
406
408
|
a single set of photon paths, by weighting each path with the
|
|
407
409
|
absorption of every wavelength. The Smartg object must have been
|
|
408
410
|
compiled with alis=True, together with alt_pp=True or pp=False.
|
|
@@ -850,6 +852,122 @@ class LocalEstimate:
|
|
|
850
852
|
)
|
|
851
853
|
|
|
852
854
|
|
|
855
|
+
# The CUDA contexts that clear_context destroyed, kept referenced so
|
|
856
|
+
# that no new context takes the place of one of them. The Smartg objects
|
|
857
|
+
# of a process share the context of pycuda.autoinit: once one of them
|
|
858
|
+
# clears it, the others must not make it current again, which crashes
|
|
859
|
+
# the driver.
|
|
860
|
+
_RELEASED_CONTEXTS: list[Any] = []
|
|
861
|
+
|
|
862
|
+
|
|
863
|
+
def _released(ctx: Any) -> bool:
|
|
864
|
+
"""Return whether ctx is a context that clear_context destroyed."""
|
|
865
|
+
return any(c is ctx for c in _RELEASED_CONTEXTS)
|
|
866
|
+
|
|
867
|
+
|
|
868
|
+
def _make_current(ctx: Any) -> bool:
|
|
869
|
+
"""Make the CUDA context ctx the current one, if it is not.
|
|
870
|
+
|
|
871
|
+
Another library of the process (JAX for instance) may have made its
|
|
872
|
+
own context current, where the kernels of SMART-G are unknown
|
|
873
|
+
('invalid resource handle'). Returns True if ctx was pushed, to be
|
|
874
|
+
popped by the caller. Raises a RuntimeError if ctx was destroyed by
|
|
875
|
+
the clear_context of another Smartg object that shared it.
|
|
876
|
+
"""
|
|
877
|
+
if ctx is None:
|
|
878
|
+
return False
|
|
879
|
+
if _released(ctx):
|
|
880
|
+
raise RuntimeError(
|
|
881
|
+
'The CUDA context of this Smartg object was destroyed by the '
|
|
882
|
+
'clear_context of another Smartg object that shared it: '
|
|
883
|
+
'create a new Smartg object.')
|
|
884
|
+
# PyCUDA only knows the contexts it pushed itself: ask the driver
|
|
885
|
+
if _driver_current_context() == ctx.handle:
|
|
886
|
+
return False
|
|
887
|
+
ctx.push()
|
|
888
|
+
return True
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
def _autoinit_current_at_exit(sg: 'Smartg') -> None:
|
|
892
|
+
"""At exit, make the pycuda.autoinit context of sg current again.
|
|
893
|
+
|
|
894
|
+
The exit handler of pycuda.autoinit pops its context, which fails
|
|
895
|
+
('cannot pop non-current context') if another library (JAX) made
|
|
896
|
+
its own context current in the meantime.
|
|
897
|
+
"""
|
|
898
|
+
import sys
|
|
899
|
+
mod = sys.modules.get('pycuda.autoinit')
|
|
900
|
+
if (sg.ctx is None or _released(sg.ctx) or mod is None
|
|
901
|
+
or getattr(mod, 'context', None) is not sg.ctx):
|
|
902
|
+
return
|
|
903
|
+
# set it current at the driver level: pushing it would leave it
|
|
904
|
+
# twice on the stack of PyCUDA, which aborts at the module cleanup
|
|
905
|
+
if _driver_current_context() == sg.ctx.handle:
|
|
906
|
+
return
|
|
907
|
+
try:
|
|
908
|
+
import ctypes
|
|
909
|
+
for name in ('libcuda.so.1', 'libcuda.so', 'nvcuda.dll'):
|
|
910
|
+
try:
|
|
911
|
+
set_current = ctypes.CDLL(name).cuCtxSetCurrent
|
|
912
|
+
break
|
|
913
|
+
except (OSError, AttributeError):
|
|
914
|
+
continue
|
|
915
|
+
else:
|
|
916
|
+
return
|
|
917
|
+
set_current(ctypes.c_void_p(sg.ctx.handle))
|
|
918
|
+
# an exit handler must not raise: the one of pycuda.autoinit, which
|
|
919
|
+
# runs next, reports a context that is still not current
|
|
920
|
+
except Exception: # noqa: BLE001, S110
|
|
921
|
+
pass
|
|
922
|
+
|
|
923
|
+
|
|
924
|
+
def _driver_current_context() -> int | None:
|
|
925
|
+
"""Handle of the current CUDA context of the driver, or None."""
|
|
926
|
+
global _CU_CTX_GET_CURRENT
|
|
927
|
+
if _CU_CTX_GET_CURRENT is None:
|
|
928
|
+
import ctypes
|
|
929
|
+
import ctypes.util
|
|
930
|
+
names = ('libcuda.so.1', 'libcuda.so', 'nvcuda.dll',
|
|
931
|
+
ctypes.util.find_library('cuda'))
|
|
932
|
+
for name in names:
|
|
933
|
+
if not name:
|
|
934
|
+
continue
|
|
935
|
+
try:
|
|
936
|
+
_CU_CTX_GET_CURRENT = ctypes.CDLL(name).cuCtxGetCurrent
|
|
937
|
+
break
|
|
938
|
+
except (OSError, AttributeError):
|
|
939
|
+
continue
|
|
940
|
+
else:
|
|
941
|
+
_CU_CTX_GET_CURRENT = False
|
|
942
|
+
if not _CU_CTX_GET_CURRENT:
|
|
943
|
+
return None
|
|
944
|
+
import ctypes
|
|
945
|
+
handle = ctypes.c_void_p()
|
|
946
|
+
if _CU_CTX_GET_CURRENT(ctypes.byref(handle)) != 0:
|
|
947
|
+
return None
|
|
948
|
+
return handle.value
|
|
949
|
+
|
|
950
|
+
|
|
951
|
+
_CU_CTX_GET_CURRENT = None
|
|
952
|
+
|
|
953
|
+
|
|
954
|
+
def _in_context(method: Callable[..., Any]) -> Callable[..., Any]:
|
|
955
|
+
"""Run a method of Smartg with its CUDA context current."""
|
|
956
|
+
|
|
957
|
+
@functools.wraps(method)
|
|
958
|
+
def wrapper(self: 'Smartg', *args: Any, **kwargs: Any) -> Any:
|
|
959
|
+
pushed = _make_current(self.ctx)
|
|
960
|
+
try:
|
|
961
|
+
return method(self, *args, **kwargs)
|
|
962
|
+
finally:
|
|
963
|
+
# run may have popped and released the context itself
|
|
964
|
+
# (autoinit=False, keep_context=False)
|
|
965
|
+
if pushed and self.ctx is not None:
|
|
966
|
+
cuda.Context.pop() # pyright: ignore[reportAttributeAccessIssue]
|
|
967
|
+
|
|
968
|
+
return wrapper
|
|
969
|
+
|
|
970
|
+
|
|
853
971
|
class Smartg:
|
|
854
972
|
"""Initialize the Smartg object.
|
|
855
973
|
|
|
@@ -872,7 +990,7 @@ class Smartg:
|
|
|
872
990
|
double : bool, optional
|
|
873
991
|
Accumulate photons table in double precision (default double).
|
|
874
992
|
alis : bool, optional
|
|
875
|
-
Use the ALIS method (Emde et al.
|
|
993
|
+
Use the ALIS method (Emde et al. 2011) for treating gaseous
|
|
876
994
|
absorption and perturbed profile. Needs alt_pp=True or
|
|
877
995
|
pp=False. The 3D objects are not available with it.
|
|
878
996
|
back : bool, optional
|
|
@@ -906,7 +1024,8 @@ class Smartg:
|
|
|
906
1024
|
Include the Sun Induced Fluorescence, an experimental option.
|
|
907
1025
|
Needs alis=True, together with alt_pp=True or pp=False.
|
|
908
1026
|
thermal : bool, optional
|
|
909
|
-
Still in dev...
|
|
1027
|
+
Still in dev... The forward mode (back=False) needs alt_pp=True
|
|
1028
|
+
in the plane parallel geometry.
|
|
910
1029
|
rng : str, optional
|
|
911
1030
|
The pseudo-random number generator to use, only 2 choice:
|
|
912
1031
|
- PHILOX
|
|
@@ -1027,6 +1146,13 @@ class Smartg:
|
|
|
1027
1146
|
"geometry (pp=True): the spherical move does not follow "
|
|
1028
1147
|
"its cells"
|
|
1029
1148
|
)
|
|
1149
|
+
if thermal and not back and pp and not alt_pp:
|
|
1150
|
+
raise ValueError(
|
|
1151
|
+
"The forward thermal emission (thermal=True, back=False) "
|
|
1152
|
+
"needs alt_pp=True in the plane parallel geometry: the "
|
|
1153
|
+
"fast move does not start the emitted photons at their "
|
|
1154
|
+
"altitude"
|
|
1155
|
+
)
|
|
1030
1156
|
|
|
1031
1157
|
if device is not None:
|
|
1032
1158
|
env_modif = {'CUDA_DEVICE': str(device)}
|
|
@@ -1091,6 +1217,10 @@ class Smartg:
|
|
|
1091
1217
|
self.ctx = make_default_context()
|
|
1092
1218
|
|
|
1093
1219
|
self.autoinit = autoinit
|
|
1220
|
+
if autoinit:
|
|
1221
|
+
# registered after the exit handler of pycuda.autoinit, so
|
|
1222
|
+
# run before it: its context must be current to be popped
|
|
1223
|
+
atexit.register(_autoinit_current_at_exit, self)
|
|
1094
1224
|
self.pp = pp
|
|
1095
1225
|
self.double = double
|
|
1096
1226
|
self.alis = alis
|
|
@@ -1197,6 +1327,9 @@ class Smartg:
|
|
|
1197
1327
|
# 3D Object mode
|
|
1198
1328
|
options.append('-DOBJ3D')
|
|
1199
1329
|
options.append('-D' + rng)
|
|
1330
|
+
# CUDA 13 deprecates double4 (warning 1444) for double4_16a and
|
|
1331
|
+
# double4_32a, which older toolkits lack
|
|
1332
|
+
options.append('-diag-suppress=1444')
|
|
1200
1333
|
# options.append('-lineinfo')
|
|
1201
1334
|
|
|
1202
1335
|
#
|
|
@@ -1208,23 +1341,29 @@ class Smartg:
|
|
|
1208
1341
|
with open(SRC_DEVICE, encoding='ascii', errors='ignore') as f:
|
|
1209
1342
|
src_device_content = f.read()
|
|
1210
1343
|
|
|
1211
|
-
# kernel compilation
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
str(
|
|
1221
|
-
|
|
1222
|
-
|
|
1344
|
+
# kernel compilation, in the context of this object (another
|
|
1345
|
+
# library may have made its own context current)
|
|
1346
|
+
pushed = _make_current(self.ctx)
|
|
1347
|
+
try:
|
|
1348
|
+
self.mod = SourceModule(
|
|
1349
|
+
src_device_content,
|
|
1350
|
+
nvcc='nvcc',
|
|
1351
|
+
options=options,
|
|
1352
|
+
no_extern_c=True,
|
|
1353
|
+
cache_dir=str(cache_dir),
|
|
1354
|
+
include_dirs=[
|
|
1355
|
+
str(DIR_SRC),
|
|
1356
|
+
str(DIR_SRC / 'incRNGs' / 'Random123'),
|
|
1357
|
+
],
|
|
1358
|
+
)
|
|
1223
1359
|
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1360
|
+
# load the kernel
|
|
1361
|
+
self.kernel = self.mod.get_function('launchKernel')
|
|
1362
|
+
# self.kernel2 = self.mod.get_function('launchKernel2')
|
|
1363
|
+
self.kernel2 = self.mod.get_function('reduce_absorption_gpu')
|
|
1364
|
+
finally:
|
|
1365
|
+
if pushed:
|
|
1366
|
+
cuda.Context.pop() # pyright: ignore[reportAttributeAccessIssue]
|
|
1228
1367
|
|
|
1229
1368
|
#
|
|
1230
1369
|
# common attributes
|
|
@@ -1280,25 +1419,34 @@ class Smartg:
|
|
|
1280
1419
|
Notes
|
|
1281
1420
|
-----
|
|
1282
1421
|
Once this method has been called, the run method can no longer
|
|
1283
|
-
be used: the Smartg object must be reinitialized.
|
|
1422
|
+
be used: the Smartg object must be reinitialized. The Smartg
|
|
1423
|
+
objects with autoinit share one context: once one of them has
|
|
1424
|
+
cleared it, the others can no longer run, and their own
|
|
1425
|
+
clear_context only prints that there is no context to clear.
|
|
1284
1426
|
"""
|
|
1285
1427
|
try:
|
|
1286
1428
|
assert self.ctx is not None
|
|
1429
|
+
# another library (JAX) may have made its context current
|
|
1430
|
+
_make_current(self.ctx)
|
|
1287
1431
|
self.ctx.pop()
|
|
1288
1432
|
self.ctx.detach()
|
|
1433
|
+
_RELEASED_CONTEXTS.append(self.ctx)
|
|
1289
1434
|
self.ctx = None
|
|
1290
1435
|
from pycuda.tools import clear_context_caches
|
|
1291
1436
|
|
|
1292
1437
|
clear_context_caches()
|
|
1293
1438
|
if self.autoinit:
|
|
1294
|
-
#
|
|
1439
|
+
# With autoinit, delete pycuda.autoinit and its exit
|
|
1440
|
+
# handler, which would pop the destroyed context
|
|
1295
1441
|
import pycuda.autoinit
|
|
1296
1442
|
|
|
1443
|
+
atexit.unregister(pycuda.autoinit._finish_up)
|
|
1297
1444
|
del pycuda.autoinit
|
|
1298
1445
|
# pycuda's own error classes again, nothing else to do here
|
|
1299
1446
|
except Exception: # noqa: BLE001
|
|
1300
1447
|
print("There is no current context to clear.")
|
|
1301
1448
|
|
|
1449
|
+
@_in_context
|
|
1302
1450
|
def run(
|
|
1303
1451
|
self,
|
|
1304
1452
|
wavelength: NumericArrayLike | BandSet | Sequence[BandLike],
|
|
@@ -3529,7 +3529,9 @@ __device__ void move_pp2(Photon* ph, struct Profile *prof_atm, struct Profile *p
|
|
|
3529
3529
|
h_cur = coef_cur * intTime1;
|
|
3530
3530
|
|
|
3531
3531
|
#ifndef ALIS
|
|
3532
|
-
|
|
3532
|
+
// the absorption of the cell: gases of the level, and the
|
|
3533
|
+
// particles of a 3D component (its own optical properties)
|
|
3534
|
+
h_cur_abs = prof[cell[ph->layer].iopt+ilam].OD_abs * intTime1;
|
|
3533
3535
|
#endif
|
|
3534
3536
|
|
|
3535
3537
|
//
|
|
@@ -3552,7 +3554,12 @@ __device__ void move_pp2(Photon* ph, struct Profile *prof_atm, struct Profile *p
|
|
|
3552
3554
|
for (int k=0; k<NLOWd; k++)
|
|
3553
3555
|
{
|
|
3554
3556
|
coef = get_OD(1,prof[cell[ph->layer].iopt + k*DL*NL]);
|
|
3555
|
-
|
|
3557
|
+
// the absorption of the particles of a 3D component,
|
|
3558
|
+
// smooth in wavelength, goes with the weights at the
|
|
3559
|
+
// nodes; that of the gases with the distances
|
|
3560
|
+
const float dabs = fmaxf(prof[cell[ph->layer].iopt + k*DL*NL].OD_abs
|
|
3561
|
+
- prof[cell[ph->layer].iabs + k*DL*NL].OD_abs, 0.F);
|
|
3562
|
+
ph->weight_sca[k] *= exp(-(coef-coef_cur+dabs)*intTime1);
|
|
3556
3563
|
}
|
|
3557
3564
|
#endif
|
|
3558
3565
|
break;
|
|
@@ -3579,7 +3586,9 @@ __device__ void move_pp2(Photon* ph, struct Profile *prof_atm, struct Profile *p
|
|
|
3579
3586
|
for (int k=0; k<NLOWd; k++)
|
|
3580
3587
|
{
|
|
3581
3588
|
coef = get_OD(1,prof[cell[ph->layer].iopt + k*DL*NL]);
|
|
3582
|
-
|
|
3589
|
+
const float dabs = fmaxf(prof[cell[ph->layer].iopt + k*DL*NL].OD_abs
|
|
3590
|
+
- prof[cell[ph->layer].iabs + k*DL*NL].OD_abs, 0.F);
|
|
3591
|
+
ph->weight_sca[k] *= __expf(-(coef-coef_cur+dabs)*intTime1);
|
|
3583
3592
|
}
|
|
3584
3593
|
#endif
|
|
3585
3594
|
|
|
@@ -4923,11 +4932,13 @@ __device__ void surfaceWaterRough(Photon* ph, int le,
|
|
|
4923
4932
|
|
|
4924
4933
|
// A refraction crosses the facet: the incident and the refracted
|
|
4925
4934
|
// directions both go against its normal, else the photon would
|
|
4926
|
-
// reach the facet from behind and there is no transmission
|
|
4935
|
+
// reach the facet from behind and there is no transmission. The
|
|
4936
|
+
// virtual photon is removed rather than given a null weight, which
|
|
4937
|
+
// the counts of photons (N_*) took for a photon
|
|
4927
4938
|
if (((ph->loc==SURF0P) && (count_level==DOWN0M) ||
|
|
4928
4939
|
(ph->loc==SURF0M) && (count_level==UP0P)) &&
|
|
4929
4940
|
((dot(no, ph->v) >= 0.F) || (dot(no, v) >= 0.F))) {
|
|
4930
|
-
ph->
|
|
4941
|
+
ph->loc=REMOVED;
|
|
4931
4942
|
return;
|
|
4932
4943
|
}
|
|
4933
4944
|
|
|
@@ -7610,14 +7621,16 @@ __device__ void countPhoton(Photon* ph, struct Spectrum *spectrum,
|
|
|
7610
7621
|
/*for (int n=0; n<(NATMd); n++){
|
|
7611
7622
|
wabs += prof_atm[n+1 + il*(NATMd+1)].OD_abs * ph->cdist_atm[n];
|
|
7612
7623
|
}*/
|
|
7624
|
+
// the 3D move adds the path in a cell to cdist_*[iabs], iabs
|
|
7625
|
+
// counted from 1 at the top: level n+1 goes with cdist_*[n+1]
|
|
7613
7626
|
for (int n=0; n<(NATM_ABSd); n++){
|
|
7614
|
-
wabs += prof_atm[n+1 + il*(NATMd+1)].OD_abs * ph->cdist_atm[n];
|
|
7627
|
+
wabs += prof_atm[n+1 + il*(NATMd+1)].OD_abs * ph->cdist_atm[n+1];
|
|
7615
7628
|
}
|
|
7616
7629
|
/*for (int n=0; n<(NOCEd); n++){
|
|
7617
7630
|
wabs += prof_oc[ n+1 + il*(NOCEd+1)].OD_abs * ph->cdist_oc[n];
|
|
7618
7631
|
}*/
|
|
7619
7632
|
for (int n=0; n<(NOCE_ABSd); n++){
|
|
7620
|
-
wabs += prof_oc[ n+1 + il*(NOCEd+1)].OD_abs * ph->cdist_oc[n];
|
|
7633
|
+
wabs += prof_oc[ n+1 + il*(NOCEd+1)].OD_abs * ph->cdist_oc[n+1];
|
|
7621
7634
|
}
|
|
7622
7635
|
#endif
|
|
7623
7636
|
|
|
@@ -7702,7 +7715,11 @@ __device__ void countPhoton(Photon* ph, struct Spectrum *spectrum,
|
|
|
7702
7715
|
#if ( defined(SPHERIQUE) || defined(ALT_PP) )
|
|
7703
7716
|
unsigned long long K = NBTHETAd*NBPHId*NSENSORd; /* number of potential output per photon*/
|
|
7704
7717
|
unsigned long long LL;
|
|
7705
|
-
|
|
7718
|
+
// Histories stored for absorption computation afterward (only
|
|
7719
|
+
// spherical || alt_pp); a count of zero intensity (a local estimate
|
|
7720
|
+
// that did not reach the level, for instance) contributes nothing
|
|
7721
|
+
if (HISTd==1 && ((count_level==UPTOA) || (count_level==DOWN0P))
|
|
7722
|
+
&& (weight*(st.x+st.y) != 0.F)) {
|
|
7706
7723
|
//int idx = blockIdx.x * blockDim.x + threadIdx.x;
|
|
7707
7724
|
unsigned long long counter2;
|
|
7708
7725
|
counter2=atomicAdd(NPhotonsOut, 1);
|
|
@@ -7821,10 +7838,10 @@ __device__ void countPhoton(Photon* ph, struct Spectrum *spectrum,
|
|
|
7821
7838
|
}
|
|
7822
7839
|
#else
|
|
7823
7840
|
for (int n=0; n<(NATM_ABSd); n++){
|
|
7824
|
-
wabs_cdist += prof_atm[n+1 + ph->ilam*(NATMd+1)].OD_abs * ph->cdist_atm[n];
|
|
7841
|
+
wabs_cdist += prof_atm[n+1 + ph->ilam*(NATMd+1)].OD_abs * ph->cdist_atm[n+1];
|
|
7825
7842
|
}
|
|
7826
7843
|
for (int n=0; n<(NOCE_ABSd); n++){
|
|
7827
|
-
wabs_cdist += prof_oc[n+1 + ph->ilam*(NOCEd+1)].OD_abs * ph->cdist_oc[n];
|
|
7844
|
+
wabs_cdist += prof_oc[n+1 + ph->ilam*(NOCEd+1)].OD_abs * ph->cdist_oc[n+1];
|
|
7828
7845
|
}
|
|
7829
7846
|
#endif
|
|
7830
7847
|
wabs_cdist = exp(-wabs_cdist);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Tests of the CUDA context of SMART-G next to the one of JAX.
|
|
2
|
+
|
|
3
|
+
JAX opens a CUDA context of its own on the GPU, where the kernels of
|
|
4
|
+
SMART-G are unknown. These tests need JAX with a GPU (the extra
|
|
5
|
+
environment) and are skipped otherwise. Run them in a pytest process of
|
|
6
|
+
their own: test_smartg_jax.py keeps JAX on the CPU for the whole
|
|
7
|
+
process.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
import pytest
|
|
14
|
+
|
|
15
|
+
from smartg.atmosphere import Atm1D
|
|
16
|
+
from smartg.smartg import LocalEstimate, Smartg
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_run_after_jax_on_gpu() -> None:
|
|
20
|
+
"""Check that Smartg runs after JAX has made its context current.
|
|
21
|
+
|
|
22
|
+
The Smartg object compiled before runs again, and a new one
|
|
23
|
+
compiles and runs, with the radiance of the first run. They failed
|
|
24
|
+
with 'invalid resource handle' before 2.0.3.
|
|
25
|
+
"""
|
|
26
|
+
# a small share of the GPU memory is enough for this test
|
|
27
|
+
os.environ.setdefault("XLA_PYTHON_CLIENT_PREALLOCATE", "false")
|
|
28
|
+
jax = pytest.importorskip("jax")
|
|
29
|
+
if not any(dv.platform == "gpu" for dv in jax.devices()):
|
|
30
|
+
pytest.skip("JAX has no GPU")
|
|
31
|
+
import jax.numpy as jnp
|
|
32
|
+
|
|
33
|
+
kwargs = {
|
|
34
|
+
"atmosphere": Atm1D("afglms"),
|
|
35
|
+
"le": LocalEstimate(th_deg=[0.0], phi_deg=[0.0]),
|
|
36
|
+
"n_photons": 1e5,
|
|
37
|
+
"seed": 1,
|
|
38
|
+
"progress": False,
|
|
39
|
+
}
|
|
40
|
+
s1 = Smartg()
|
|
41
|
+
i1 = s1.run(550.0, **kwargs)["I_up (TOA)"].values
|
|
42
|
+
float(jnp.sum(jnp.arange(10.0)))
|
|
43
|
+
i2 = s1.run(550.0, **kwargs)["I_up (TOA)"].values
|
|
44
|
+
i3 = Smartg(alt_pp=True).run(550.0, **kwargs)["I_up (TOA)"].values
|
|
45
|
+
np.testing.assert_allclose(i2, i1, rtol=0.05)
|
|
46
|
+
np.testing.assert_allclose(i3, i1, rtol=0.05)
|
|
@@ -6,6 +6,10 @@ the outputs and the arguments the run accepts.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import math
|
|
9
|
+
import subprocess
|
|
10
|
+
import sys
|
|
11
|
+
import textwrap
|
|
12
|
+
from pathlib import Path
|
|
9
13
|
from typing import Any
|
|
10
14
|
|
|
11
15
|
import numpy as np
|
|
@@ -17,7 +21,7 @@ from pycuda.compiler import SourceModule
|
|
|
17
21
|
import smartg.smartg as smartg_mod
|
|
18
22
|
from smartg import conftest
|
|
19
23
|
from smartg.albedo import AlbedoCst, AlbedoMap
|
|
20
|
-
from smartg.atmosphere import AerOPAC, Atm1D, Atm3D, Cloud
|
|
24
|
+
from smartg.atmosphere import Aer3D, AerOPAC, Atm1D, Atm3D, Cloud, od2k
|
|
21
25
|
from smartg.cdf import icdf_2d
|
|
22
26
|
from smartg.grid3d import Grid3D
|
|
23
27
|
from smartg.reptran import Reptran, reduce_reptran
|
|
@@ -72,6 +76,38 @@ def test_basic(request: pytest.FixtureRequest) -> None:
|
|
|
72
76
|
conftest.savefig(request)
|
|
73
77
|
|
|
74
78
|
|
|
79
|
+
def test_clear_context_leaves_nothing_to_pop_at_exit() -> None:
|
|
80
|
+
"""Check that clear_context leaves pycuda no context to pop at exit.
|
|
81
|
+
|
|
82
|
+
Two Smartg objects of one process run in turn, each cleared after
|
|
83
|
+
its run. The exit handler of pycuda.autoinit used to pop the
|
|
84
|
+
contexts that clear_context had destroyed, and printed 'context::pop
|
|
85
|
+
failed' twice. The script runs in a subprocess, whose exit is the
|
|
86
|
+
point.
|
|
87
|
+
"""
|
|
88
|
+
script = textwrap.dedent(
|
|
89
|
+
"""
|
|
90
|
+
from smartg.atmosphere import Atm1D
|
|
91
|
+
from smartg.smartg import Smartg
|
|
92
|
+
|
|
93
|
+
for alt_pp in (False, True):
|
|
94
|
+
sg = Smartg(alt_pp=alt_pp)
|
|
95
|
+
sg.run(550.0, atmosphere=Atm1D("afglt"), n_photons=1e4,
|
|
96
|
+
progress=False)
|
|
97
|
+
sg.clear_context()
|
|
98
|
+
print("done")
|
|
99
|
+
"""
|
|
100
|
+
)
|
|
101
|
+
out = subprocess.run(
|
|
102
|
+
[sys.executable, "-c", script], capture_output=True, text=True,
|
|
103
|
+
timeout=600, check=False,
|
|
104
|
+
)
|
|
105
|
+
assert out.returncode == 0, out.stderr[-2000:]
|
|
106
|
+
assert "done" in out.stdout
|
|
107
|
+
assert "There is no current context" not in out.stdout
|
|
108
|
+
assert "context::pop failed" not in out.stderr
|
|
109
|
+
|
|
110
|
+
|
|
75
111
|
@pytest.mark.parametrize("wavelength", wavelength_list)
|
|
76
112
|
def test_atm(sg: Smartg, wavelength: Wavelength) -> None:
|
|
77
113
|
"""Check that a run keeps a wavelength axis only for a list."""
|
|
@@ -1130,6 +1166,108 @@ def test_brdf_surface_in_3d_atmosphere() -> None:
|
|
|
1130
1166
|
)
|
|
1131
1167
|
|
|
1132
1168
|
|
|
1169
|
+
def _scene_3d(
|
|
1170
|
+
comp_3d: list[Aer3D] | None = None,
|
|
1171
|
+
mol_abs_1d: NDArray[np.floating] | None = None,
|
|
1172
|
+
) -> dict[str, Any]:
|
|
1173
|
+
"""Return the run arguments of a periodic 3D atmosphere.
|
|
1174
|
+
|
|
1175
|
+
The AFGL US standard atmosphere on 7 layers, 10 km wide with
|
|
1176
|
+
0.25 km columns along x, at 430 and 440 nm. Six sensors at the
|
|
1177
|
+
TOA above x 4.75-5.25 km look at it in backward mode, through a
|
|
1178
|
+
local estimate at 40 degrees, over a Lambertian ground of albedo
|
|
1179
|
+
0.3. mol_abs_1d replaces the absorption coefficients of the
|
|
1180
|
+
levels (km-1, levels from the top).
|
|
1181
|
+
"""
|
|
1182
|
+
z = np.array([100.0, 60.0, 30.0, 15.0, 8.0, 4.0, 1.0, 0.0])
|
|
1183
|
+
dx = 0.25
|
|
1184
|
+
grid3 = Grid3D(
|
|
1185
|
+
np.arange(0.0, 10.0 + dx / 2, dx),
|
|
1186
|
+
np.array([0.0, 4.0, 6.0, 10.0]),
|
|
1187
|
+
z[::-1],
|
|
1188
|
+
periodic=True,
|
|
1189
|
+
)
|
|
1190
|
+
wavelengths = np.array([430.0, 440.0])
|
|
1191
|
+
p1 = Atm1D("afglus", grid=z).calc(wavelengths)
|
|
1192
|
+
if mol_abs_1d is None:
|
|
1193
|
+
mol_abs_1d = np.asarray(od2k(p1, "OD_g"))
|
|
1194
|
+
atmosphere = Atm3D(
|
|
1195
|
+
atm_1d=Atm1D("afglus"),
|
|
1196
|
+
grid_3d=grid3,
|
|
1197
|
+
comp_3d=comp_3d,
|
|
1198
|
+
mol_sca_1d=np.asarray(od2k(p1, "OD_r")),
|
|
1199
|
+
mol_abs_1d=mol_abs_1d,
|
|
1200
|
+
).calc(wavelengths)
|
|
1201
|
+
sensors = get_sensors_grid(
|
|
1202
|
+
np.array([4.75, 5.0, 5.25]), np.array([4.0, 6.0]), pos_z=100.0,
|
|
1203
|
+
th_deg=180.0, ph_deg=0.0, loc="ATMOS", grid_3d=grid3,
|
|
1204
|
+
)
|
|
1205
|
+
return {
|
|
1206
|
+
"wavelength": wavelengths,
|
|
1207
|
+
"atmosphere": atmosphere,
|
|
1208
|
+
"surface": LambSurface(alb=AlbedoCst(0.3)),
|
|
1209
|
+
"sensor": sensors,
|
|
1210
|
+
"le": LocalEstimate(
|
|
1211
|
+
th_deg=[40.0], phi_deg=[180.0], count_level=[0]
|
|
1212
|
+
),
|
|
1213
|
+
"n_photons": 2e5,
|
|
1214
|
+
"seed": 1,
|
|
1215
|
+
"stdev": True,
|
|
1216
|
+
"progress": False,
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
|
|
1220
|
+
def test_3d_alis_absorbs_each_layer_along_its_path() -> None:
|
|
1221
|
+
"""Check that 3D ALIS applies each layer's absorption to its path.
|
|
1222
|
+
|
|
1223
|
+
Only the lowest layer (0-1 km) of a horizontally uniform 3D
|
|
1224
|
+
atmosphere absorbs. ALIS, which applies the absorption to the path
|
|
1225
|
+
lengths when the photon is counted, must agree with the
|
|
1226
|
+
Beer-Lambert absorption of the move. Before 2.0.3 the count paired
|
|
1227
|
+
each layer with the path in the layer above: here the 1-4 km layer,
|
|
1228
|
+
three times thicker.
|
|
1229
|
+
"""
|
|
1230
|
+
kabs = np.zeros((2, 8)) # (wavelength, level from the top)
|
|
1231
|
+
kabs[:, -1] = 0.5
|
|
1232
|
+
kwargs = _scene_3d(mol_abs_1d=kabs)
|
|
1233
|
+
beer = Smartg(opt3d=True, alt_pp=True, back=True, double=True).run(
|
|
1234
|
+
beer=1, **kwargs
|
|
1235
|
+
)
|
|
1236
|
+
alis = Smartg(
|
|
1237
|
+
opt3d=True, alt_pp=True, alis=True, back=True, double=True
|
|
1238
|
+
).run(alis_options=Alis(n_low=2), **kwargs)
|
|
1239
|
+
assert np.all(np.abs(_z_scores(alis, beer, "up (TOA)")) < 4)
|
|
1240
|
+
|
|
1241
|
+
|
|
1242
|
+
def test_absorbing_3d_particles_with_beer_and_alis() -> None:
|
|
1243
|
+
"""Check that the particles of a 3D component absorb in every mode.
|
|
1244
|
+
|
|
1245
|
+
A plume of desert particles of single scattering albedo 0.8 fills
|
|
1246
|
+
x 4-6 km and z 0-4 km under the sensors. Its absorption must be the
|
|
1247
|
+
same whether the photon weight carries it through the single
|
|
1248
|
+
scattering albedo (beer=0), the Beer-Lambert law (beer=1) or the
|
|
1249
|
+
ALIS weights. Before 2.0.3 the last two read the absorption of the
|
|
1250
|
+
1D level, without the particles: 1.6 times the beer=0 radiance.
|
|
1251
|
+
"""
|
|
1252
|
+
# 1-based (ix, iy, iz): x 4-6 km, the middle y cell, z 0-4 km
|
|
1253
|
+
cells = np.array(
|
|
1254
|
+
[[i, 2, k] for i in range(17, 25) for k in (1, 2)], dtype=np.int32
|
|
1255
|
+
)
|
|
1256
|
+
plume = Aer3D(
|
|
1257
|
+
"desert", w_ref=435.0, ext_ref=np.full(len(cells), 1.0),
|
|
1258
|
+
cell_indices=cells, rh=np.full(len(cells), 50.0), ssa_cst=0.8,
|
|
1259
|
+
)
|
|
1260
|
+
kwargs = _scene_3d(comp_3d=[plume])
|
|
1261
|
+
sg = Smartg(opt3d=True, alt_pp=True, back=True, double=True)
|
|
1262
|
+
ssa = sg.run(beer=0, **kwargs)
|
|
1263
|
+
beer = sg.run(beer=1, **kwargs)
|
|
1264
|
+
alis = Smartg(
|
|
1265
|
+
opt3d=True, alt_pp=True, alis=True, back=True, double=True
|
|
1266
|
+
).run(alis_options=Alis(n_low=2), **kwargs)
|
|
1267
|
+
assert np.all(np.abs(_z_scores(beer, ssa, "up (TOA)")) < 4)
|
|
1268
|
+
assert np.all(np.abs(_z_scores(alis, ssa, "up (TOA)")) < 4)
|
|
1269
|
+
|
|
1270
|
+
|
|
1133
1271
|
def _sza_max_run(
|
|
1134
1272
|
sg: Smartg, n_theta: int, sza_max: float, seed: int
|
|
1135
1273
|
) -> xr.Dataset:
|
|
@@ -1621,3 +1759,37 @@ def test_sensor_on_either_side_of_the_surface(name: str, alt_pp: bool) -> None:
|
|
|
1621
1759
|
sb = bw["I_stdev_up (TOA)"].values.ravel()[0]
|
|
1622
1760
|
assert b > 0.1
|
|
1623
1761
|
assert abs(a - b) < 5 * np.hypot(sa, sb), (name, a, sa, b, sb)
|
|
1762
|
+
|
|
1763
|
+
|
|
1764
|
+
def test_clear_context_of_two_objects() -> None:
|
|
1765
|
+
"""Check that two Smartg objects sharing a context clear safely.
|
|
1766
|
+
|
|
1767
|
+
Both run in the context of pycuda.autoinit: once the first has
|
|
1768
|
+
cleared it, the second refuses to run and to clear it again, where
|
|
1769
|
+
it crashed the driver, and a new Smartg object runs. The objects
|
|
1770
|
+
live in a subprocess, the context being that of the whole process.
|
|
1771
|
+
"""
|
|
1772
|
+
code = textwrap.dedent("""
|
|
1773
|
+
from smartg.atmosphere import Atm1D
|
|
1774
|
+
from smartg.smartg import Smartg
|
|
1775
|
+
a, b = Smartg(), Smartg(alt_pp=True)
|
|
1776
|
+
a.clear_context()
|
|
1777
|
+
b.clear_context()
|
|
1778
|
+
try:
|
|
1779
|
+
b.run(500.0, n_photons=1e3, progress=False)
|
|
1780
|
+
except RuntimeError as err:
|
|
1781
|
+
print("refused:", err)
|
|
1782
|
+
c = Smartg()
|
|
1783
|
+
m = c.run(500.0, atmosphere=Atm1D("afglms"), n_photons=1e4,
|
|
1784
|
+
progress=False)
|
|
1785
|
+
print("run:", float(m["I_up (TOA)"].mean()))
|
|
1786
|
+
c.clear_context()
|
|
1787
|
+
""")
|
|
1788
|
+
proc = subprocess.run(
|
|
1789
|
+
[sys.executable, "-B", "-c", code], capture_output=True, text=True,
|
|
1790
|
+
cwd=Path(__file__).resolve().parents[2], timeout=600, check=False,
|
|
1791
|
+
)
|
|
1792
|
+
assert proc.returncode == 0, proc.stderr[-2000:]
|
|
1793
|
+
assert "There is no current context to clear." in proc.stdout
|
|
1794
|
+
assert "refused: The CUDA context of this Smartg" in proc.stdout
|
|
1795
|
+
assert "run: " in proc.stdout
|