From d4018658259a382d3f22ced3b43c560e476712b5 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Tue, 4 Jun 2024 16:41:30 +0200 Subject: [PATCH 01/14] wip ship st test --- tests/conftest.py | 23 ++++++++ tests/instruments/test_ship_underwater_st.py | 54 ++++++++++++++++--- .../instruments/ship_underwater_st.py | 10 ++-- virtual_ship/spacetime.py | 3 +- 4 files changed, 80 insertions(+), 10 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index f656c38f..57475f96 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,9 @@ """Test configuration that is ran for every test.""" import pytest +import tempfile +import shutil +from typing import Generator, Callable @pytest.fixture(autouse=True) @@ -12,3 +15,23 @@ def change_test_dir(request, monkeypatch): :param monkeypatch: - """ monkeypatch.chdir(request.fspath.dirname) + + +@pytest.fixture +def tmp_dir_factory() -> Generator[Callable[[str], str], None, None]: + """ + Returns a functions that can generate a random directory and returns its path. + + Created directories are automatically deleted after the rest. + """ + created_dirs = [] + + def _create_temp_dir(suffix: str): + dir_path = tempfile.mkdtemp(suffix=suffix) + created_dirs.append(dir_path) + return dir_path + + yield _create_temp_dir + + for dir_path in created_dirs: + shutil.rmtree(dir_path) diff --git a/tests/instruments/test_ship_underwater_st.py b/tests/instruments/test_ship_underwater_st.py index 05de1d49..ac2756c7 100644 --- a/tests/instruments/test_ship_underwater_st.py +++ b/tests/instruments/test_ship_underwater_st.py @@ -5,25 +5,67 @@ from virtual_ship import Location, Spacetime from virtual_ship.instruments.ship_underwater_st import simulate_ship_underwater_st +import xarray as xr +from typing import Callable -def test_simulate_ship_underwater_st() -> None: +def test_simulate_ship_underwater_st(tmp_dir_factory: Callable[[str], str]) -> None: DEPTH = -2 + base_time = np.datetime64("1950-01-01") + + variables = ["salinity", "temperature", "lat", "lon"] + + sample_points = [ + Spacetime(Location(3, 0), base_time + np.timedelta64(0, "s")), + Spacetime(Location(7, 0), base_time + np.timedelta64(1, "s")), + ] + expected_obs = [ + {"salinity": 1, "temperature": 2, "lat": 3, "lon": 0}, + {"salinity": 5, "temperature": 6, "lat": 7, "lon": 0}, + ] + fieldset = FieldSet.from_data( - {"U": 0, "V": 0, "S": 0, "T": 0}, + { + "U": np.zeros((2, 2)), + "V": np.zeros((2, 2)), + "salinity": [ + [expected_obs[0]["salinity"], 0], + [0, expected_obs[1]["salinity"]], + ], + "temperature": [ + [expected_obs[0]["temperature"], 0], + [0, expected_obs[1]["temperature"]], + ], + }, { "lon": 0, - "lat": 0, - "time": [np.datetime64("1950-01-01") + np.timedelta64(632160, "h")], + "lat": np.array([expected_obs[0]["lat"], expected_obs[1]["lat"]]), + "time": np.array([base_time, base_time + np.timedelta64(1, "s")]), }, ) - sample_points = [Spacetime(Location(0, 0), 0)] + out_file_name = tmp_dir_factory(suffix=".zarr") simulate_ship_underwater_st( fieldset=fieldset, - out_file_name="test", + out_file_name=out_file_name, depth=DEPTH, sample_points=sample_points, ) + + results = xr.open_zarr(out_file_name) + + assert len(results.trajectory) == 1 + assert len(results.sel(trajectory=0).obs == len(sample_points)) + + for i, (obs_i, exp) in enumerate( + zip(results.sel(trajectory=0).obs, expected_obs, strict=True) + ): + obs = results.sel(trajectory=0, obs=obs_i) + for var in variables: + obs_value = obs[var].values.item() + exp_value = exp[var] + assert np.isclose( + obs_value, exp_value + ), f"Observation incorrect {i=} {var=} {obs_value=} {exp_value=}." diff --git a/virtual_ship/instruments/ship_underwater_st.py b/virtual_ship/instruments/ship_underwater_st.py index 389a1e8e..5a5b1e0b 100644 --- a/virtual_ship/instruments/ship_underwater_st.py +++ b/virtual_ship/instruments/ship_underwater_st.py @@ -15,12 +15,16 @@ # define function sampling Salinity def _sample_salinity(particle, fieldset, time): - particle.salinity = fieldset.S[time, particle.depth, particle.lat, particle.lon] + particle.salinity = fieldset.salinity[ + time, particle.depth, particle.lat, particle.lon + ] # define function sampling Temperature def _sample_temperature(particle, fieldset, time): - particle.temperature = fieldset.T[time, particle.depth, particle.lat, particle.lon] + particle.temperature = fieldset.temperature[ + time, particle.depth, particle.lat, particle.lon + ] def simulate_ship_underwater_st( @@ -56,7 +60,7 @@ def simulate_ship_underwater_st( for point in sample_points: particleset.lon_nextloop[:] = point.location.lon particleset.lat_nextloop[:] = point.location.lat - particleset.time_nextloop[:] = point.time + particleset.time_nextloop[:] = fieldset.time_origin.reltime(point.time) particleset.execute( [_sample_salinity, _sample_temperature], diff --git a/virtual_ship/spacetime.py b/virtual_ship/spacetime.py index c277e642..c133a4f6 100644 --- a/virtual_ship/spacetime.py +++ b/virtual_ship/spacetime.py @@ -3,6 +3,7 @@ from dataclasses import dataclass from .location import Location +import numpy as np @dataclass @@ -11,4 +12,4 @@ class Spacetime: """A location and time.""" location: Location - time: float + time: np.datetime64 From 6c34b5ea591f279c0387e4fd1fca27bff3405f33 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Wed, 5 Jun 2024 16:59:29 +0200 Subject: [PATCH 02/14] finish test --- tests/instruments/test_ship_underwater_st.py | 15 +++++++++---- .../instruments/ship_underwater_st.py | 22 +++++++++++++------ 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/tests/instruments/test_ship_underwater_st.py b/tests/instruments/test_ship_underwater_st.py index ac2756c7..738b8f0b 100644 --- a/tests/instruments/test_ship_underwater_st.py +++ b/tests/instruments/test_ship_underwater_st.py @@ -7,6 +7,7 @@ from virtual_ship.instruments.ship_underwater_st import simulate_ship_underwater_st import xarray as xr from typing import Callable +from datetime import timedelta def test_simulate_ship_underwater_st(tmp_dir_factory: Callable[[str], str]) -> None: @@ -21,8 +22,14 @@ def test_simulate_ship_underwater_st(tmp_dir_factory: Callable[[str], str]) -> N Spacetime(Location(7, 0), base_time + np.timedelta64(1, "s")), ] expected_obs = [ - {"salinity": 1, "temperature": 2, "lat": 3, "lon": 0}, - {"salinity": 5, "temperature": 6, "lat": 7, "lon": 0}, + {"salinity": 1, "temperature": 2, "lat": 3, "lon": 0, "time": base_time}, + { + "salinity": 5, + "temperature": 6, + "lat": 7, + "lon": 0, + "time": base_time + np.timedelta64(1, "s"), + }, ] fieldset = FieldSet.from_data( @@ -41,7 +48,7 @@ def test_simulate_ship_underwater_st(tmp_dir_factory: Callable[[str], str]) -> N { "lon": 0, "lat": np.array([expected_obs[0]["lat"], expected_obs[1]["lat"]]), - "time": np.array([base_time, base_time + np.timedelta64(1, "s")]), + "time": np.array([expected_obs[0]["time"], expected_obs[1]["time"]]), }, ) @@ -57,7 +64,7 @@ def test_simulate_ship_underwater_st(tmp_dir_factory: Callable[[str], str]) -> N results = xr.open_zarr(out_file_name) assert len(results.trajectory) == 1 - assert len(results.sel(trajectory=0).obs == len(sample_points)) + assert len(results.sel(trajectory=0).obs) == len(sample_points) for i, (obs_i, exp) in enumerate( zip(results.sel(trajectory=0).obs, expected_obs, strict=True) diff --git a/virtual_ship/instruments/ship_underwater_st.py b/virtual_ship/instruments/ship_underwater_st.py index 5a5b1e0b..fcd36c13 100644 --- a/virtual_ship/instruments/ship_underwater_st.py +++ b/virtual_ship/instruments/ship_underwater_st.py @@ -1,11 +1,14 @@ """Ship salinity and temperature.""" import numpy as np -from parcels import FieldSet, JITParticle, ParticleSet, Variable +from parcels import FieldSet, ScipyParticle, ParticleSet, Variable from ..spacetime import Spacetime -_ShipSTParticle = JITParticle.add_variables( +# we specifically use ScipyParticle because we have many small calls to execute +# JITParticle would require compilation every time +# this ends up being faster +_ShipSTParticle = ScipyParticle.add_variables( [ Variable("salinity", dtype=np.float32, initial=np.nan), Variable("temperature", dtype=np.float32, initial=np.nan), @@ -40,32 +43,37 @@ def simulate_ship_underwater_st( :param out_file_name: The file to write the results to. :param depth: The depth at which to measure. 0 is water surface, negative is into the water. :param sample_points: The places and times to sample at. + :param sample_dt: Time between each sample point. + :param output_dt: Period of writing to output file. """ sample_points.sort(key=lambda p: p.time) particleset = ParticleSet.from_list( fieldset=fieldset, pclass=_ShipSTParticle, - lon=0.0, # initial lat/lon are irrelevant and will be overruled later. + lon=0.0, # initial lat/lon are irrelevant and will be overruled later lat=0.0, depth=depth, time=0, # same for time ) # define output file for the simulation - out_file = particleset.ParticleFile( - name=out_file_name, - ) + # the default outputdt is good(infinite), as we want to just want to write at the end of every call to 'execute' + out_file = particleset.ParticleFile(name=out_file_name) + # iterate over each points, manually set lat lon time, then + # execute the particle set for one step, performing one set of measurement for point in sample_points: particleset.lon_nextloop[:] = point.location.lon particleset.lat_nextloop[:] = point.location.lat particleset.time_nextloop[:] = fieldset.time_origin.reltime(point.time) + # perform one step using the particleset + # dt and runtime are set so exactly one step is made. particleset.execute( [_sample_salinity, _sample_temperature], dt=1, runtime=1, verbose_progress=False, + output_file=out_file, ) - out_file.write(particleset, time=particleset[0].time) From 8184c745401ad6385d435c02cc29de5a429694a2 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Wed, 5 Jun 2024 17:07:27 +0200 Subject: [PATCH 03/14] explicitly use the trajectory listed in zarr --- tests/instruments/test_ship_underwater_st.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/instruments/test_ship_underwater_st.py b/tests/instruments/test_ship_underwater_st.py index 738b8f0b..71cba266 100644 --- a/tests/instruments/test_ship_underwater_st.py +++ b/tests/instruments/test_ship_underwater_st.py @@ -64,12 +64,13 @@ def test_simulate_ship_underwater_st(tmp_dir_factory: Callable[[str], str]) -> N results = xr.open_zarr(out_file_name) assert len(results.trajectory) == 1 - assert len(results.sel(trajectory=0).obs) == len(sample_points) + traj = results.trajectory.item() + assert len(results.sel(trajectory=traj).obs) == len(sample_points) for i, (obs_i, exp) in enumerate( - zip(results.sel(trajectory=0).obs, expected_obs, strict=True) + zip(results.sel(trajectory=traj).obs, expected_obs, strict=True) ): - obs = results.sel(trajectory=0, obs=obs_i) + obs = results.sel(trajectory=traj, obs=obs_i) for var in variables: obs_value = obs[var].values.item() exp_value = exp[var] From 68db417c5f951eb5fc0434293e19cc480033d969 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Wed, 5 Jun 2024 17:20:55 +0200 Subject: [PATCH 04/14] improve comments in test --- tests/instruments/test_ship_underwater_st.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/tests/instruments/test_ship_underwater_st.py b/tests/instruments/test_ship_underwater_st.py index 71cba266..15b0adf6 100644 --- a/tests/instruments/test_ship_underwater_st.py +++ b/tests/instruments/test_ship_underwater_st.py @@ -7,20 +7,25 @@ from virtual_ship.instruments.ship_underwater_st import simulate_ship_underwater_st import xarray as xr from typing import Callable -from datetime import timedelta def test_simulate_ship_underwater_st(tmp_dir_factory: Callable[[str], str]) -> None: + # depth at which the sampling will be done DEPTH = -2 + # arbitrary time offset for the dummy fieldset base_time = np.datetime64("1950-01-01") + # variabes we are going to compare between expected and actual observations variables = ["salinity", "temperature", "lat", "lon"] + # were to sample sample_points = [ Spacetime(Location(3, 0), base_time + np.timedelta64(0, "s")), Spacetime(Location(7, 0), base_time + np.timedelta64(1, "s")), ] + + # expected observations at sample points expected_obs = [ {"salinity": 1, "temperature": 2, "lat": 3, "lon": 0, "time": base_time}, { @@ -32,6 +37,7 @@ def test_simulate_ship_underwater_st(tmp_dir_factory: Callable[[str], str]) -> N }, ] + # create fieldset based on the expected observations fieldset = FieldSet.from_data( { "U": np.zeros((2, 2)), @@ -63,10 +69,14 @@ def test_simulate_ship_underwater_st(tmp_dir_factory: Callable[[str], str]) -> N results = xr.open_zarr(out_file_name) - assert len(results.trajectory) == 1 + # below we assert if output makes sense + assert len(results.trajectory) == 1 # expect a singe trajectory traj = results.trajectory.item() - assert len(results.sel(trajectory=traj).obs) == len(sample_points) + assert len(results.sel(trajectory=traj).obs) == len( + sample_points + ) # expect as many obs as sample points + # for every obs, check if the variables match the expected observations for i, (obs_i, exp) in enumerate( zip(results.sel(trajectory=traj).obs, expected_obs, strict=True) ): From a790c25ac7e49ffc74140184c18c7aa3445b54b4 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Wed, 5 Jun 2024 17:22:59 +0200 Subject: [PATCH 05/14] docstring improvements --- tests/conftest.py | 11 +++++++---- tests/instruments/test_ship_underwater_st.py | 5 +++-- virtual_ship/instruments/ship_underwater_st.py | 4 +--- virtual_ship/spacetime.py | 3 ++- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 57475f96..da9a4e83 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,9 +1,10 @@ """Test configuration that is ran for every test.""" -import pytest -import tempfile import shutil -from typing import Generator, Callable +import tempfile +from typing import Callable, Generator + +import pytest @pytest.fixture(autouse=True) @@ -20,9 +21,11 @@ def change_test_dir(request, monkeypatch): @pytest.fixture def tmp_dir_factory() -> Generator[Callable[[str], str], None, None]: """ - Returns a functions that can generate a random directory and returns its path. + Return a function that can generate a random directory and returns its path. Created directories are automatically deleted after the rest. + + :yields: A generator for temporary directories that will be automatically removed. """ created_dirs = [] diff --git a/tests/instruments/test_ship_underwater_st.py b/tests/instruments/test_ship_underwater_st.py index 15b0adf6..e7564280 100644 --- a/tests/instruments/test_ship_underwater_st.py +++ b/tests/instruments/test_ship_underwater_st.py @@ -1,12 +1,13 @@ """Test the simulation of ship salinity temperature measurements.""" +from typing import Callable + import numpy as np +import xarray as xr from parcels import FieldSet from virtual_ship import Location, Spacetime from virtual_ship.instruments.ship_underwater_st import simulate_ship_underwater_st -import xarray as xr -from typing import Callable def test_simulate_ship_underwater_st(tmp_dir_factory: Callable[[str], str]) -> None: diff --git a/virtual_ship/instruments/ship_underwater_st.py b/virtual_ship/instruments/ship_underwater_st.py index fcd36c13..affb2a11 100644 --- a/virtual_ship/instruments/ship_underwater_st.py +++ b/virtual_ship/instruments/ship_underwater_st.py @@ -1,7 +1,7 @@ """Ship salinity and temperature.""" import numpy as np -from parcels import FieldSet, ScipyParticle, ParticleSet, Variable +from parcels import FieldSet, ParticleSet, ScipyParticle, Variable from ..spacetime import Spacetime @@ -43,8 +43,6 @@ def simulate_ship_underwater_st( :param out_file_name: The file to write the results to. :param depth: The depth at which to measure. 0 is water surface, negative is into the water. :param sample_points: The places and times to sample at. - :param sample_dt: Time between each sample point. - :param output_dt: Period of writing to output file. """ sample_points.sort(key=lambda p: p.time) diff --git a/virtual_ship/spacetime.py b/virtual_ship/spacetime.py index c133a4f6..68985669 100644 --- a/virtual_ship/spacetime.py +++ b/virtual_ship/spacetime.py @@ -2,9 +2,10 @@ from dataclasses import dataclass -from .location import Location import numpy as np +from .location import Location + @dataclass # TODO I take suggestions for a better name From 8ee267b300e976815d6360d1c60d27b3a6de8411 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Wed, 5 Jun 2024 17:24:24 +0200 Subject: [PATCH 06/14] fix typo --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index da9a4e83..5a0f5a65 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -23,7 +23,7 @@ def tmp_dir_factory() -> Generator[Callable[[str], str], None, None]: """ Return a function that can generate a random directory and returns its path. - Created directories are automatically deleted after the rest. + Created directories are automatically deleted after the test. :yields: A generator for temporary directories that will be automatically removed. """ From 4c04e47919aeb85e5098a7d44a5656c2081372a8 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Wed, 5 Jun 2024 17:25:24 +0200 Subject: [PATCH 07/14] fix typo --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 5a0f5a65..f749d1da 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -21,7 +21,7 @@ def change_test_dir(request, monkeypatch): @pytest.fixture def tmp_dir_factory() -> Generator[Callable[[str], str], None, None]: """ - Return a function that can generate a random directory and returns its path. + Yields a function that can generate a random directory and returns its path. Created directories are automatically deleted after the test. From 250344c0ffb8fbb2fae78ef62f3560658321a8ee Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Wed, 5 Jun 2024 17:26:32 +0200 Subject: [PATCH 08/14] fix typo --- tests/instruments/test_ship_underwater_st.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/instruments/test_ship_underwater_st.py b/tests/instruments/test_ship_underwater_st.py index e7564280..8ba55561 100644 --- a/tests/instruments/test_ship_underwater_st.py +++ b/tests/instruments/test_ship_underwater_st.py @@ -20,7 +20,7 @@ def test_simulate_ship_underwater_st(tmp_dir_factory: Callable[[str], str]) -> N # variabes we are going to compare between expected and actual observations variables = ["salinity", "temperature", "lat", "lon"] - # were to sample + # where to sample sample_points = [ Spacetime(Location(3, 0), base_time + np.timedelta64(0, "s")), Spacetime(Location(7, 0), base_time + np.timedelta64(1, "s")), From 6a936cbef2352c2084733f4b7c64a34753bb80f6 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Wed, 5 Jun 2024 17:40:19 +0200 Subject: [PATCH 09/14] spacetime time now datetime object isntead of numpy --- tests/conftest.py | 2 +- tests/instruments/test_ship_underwater_st.py | 24 ++++++++++++++----- .../instruments/ship_underwater_st.py | 4 +++- virtual_ship/spacetime.py | 5 ++-- 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index f749d1da..dfeb93b7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -21,7 +21,7 @@ def change_test_dir(request, monkeypatch): @pytest.fixture def tmp_dir_factory() -> Generator[Callable[[str], str], None, None]: """ - Yields a function that can generate a random directory and returns its path. + Yield functions that can generate a random directory and returns its path. Created directories are automatically deleted after the test. diff --git a/tests/instruments/test_ship_underwater_st.py b/tests/instruments/test_ship_underwater_st.py index 8ba55561..b1294557 100644 --- a/tests/instruments/test_ship_underwater_st.py +++ b/tests/instruments/test_ship_underwater_st.py @@ -1,5 +1,6 @@ """Test the simulation of ship salinity temperature measurements.""" +import datetime from typing import Callable import numpy as np @@ -15,26 +16,32 @@ def test_simulate_ship_underwater_st(tmp_dir_factory: Callable[[str], str]) -> N DEPTH = -2 # arbitrary time offset for the dummy fieldset - base_time = np.datetime64("1950-01-01") + base_time = datetime.datetime.strptime("1950-01-01", "%Y-%m-%d") # variabes we are going to compare between expected and actual observations variables = ["salinity", "temperature", "lat", "lon"] # where to sample sample_points = [ - Spacetime(Location(3, 0), base_time + np.timedelta64(0, "s")), - Spacetime(Location(7, 0), base_time + np.timedelta64(1, "s")), + Spacetime(Location(3, 0), base_time + datetime.timedelta(seconds=0)), + Spacetime(Location(7, 0), base_time + datetime.timedelta(seconds=1)), ] # expected observations at sample points expected_obs = [ - {"salinity": 1, "temperature": 2, "lat": 3, "lon": 0, "time": base_time}, + { + "salinity": 1, + "temperature": 2, + "lat": 3, + "lon": 0, + "time": base_time + datetime.timedelta(seconds=0), + }, { "salinity": 5, "temperature": 6, "lat": 7, "lon": 0, - "time": base_time + np.timedelta64(1, "s"), + "time": base_time + datetime.timedelta(seconds=1), }, ] @@ -55,7 +62,12 @@ def test_simulate_ship_underwater_st(tmp_dir_factory: Callable[[str], str]) -> N { "lon": 0, "lat": np.array([expected_obs[0]["lat"], expected_obs[1]["lat"]]), - "time": np.array([expected_obs[0]["time"], expected_obs[1]["time"]]), + "time": np.array( + [ + np.datetime64(expected_obs[0]["time"]), + np.datetime64(expected_obs[1]["time"]), + ] + ), }, ) diff --git a/virtual_ship/instruments/ship_underwater_st.py b/virtual_ship/instruments/ship_underwater_st.py index affb2a11..323baec0 100644 --- a/virtual_ship/instruments/ship_underwater_st.py +++ b/virtual_ship/instruments/ship_underwater_st.py @@ -64,7 +64,9 @@ def simulate_ship_underwater_st( for point in sample_points: particleset.lon_nextloop[:] = point.location.lon particleset.lat_nextloop[:] = point.location.lat - particleset.time_nextloop[:] = fieldset.time_origin.reltime(point.time) + particleset.time_nextloop[:] = fieldset.time_origin.reltime( + np.datetime64(point.time) + ) # perform one step using the particleset # dt and runtime are set so exactly one step is made. diff --git a/virtual_ship/spacetime.py b/virtual_ship/spacetime.py index 68985669..03804975 100644 --- a/virtual_ship/spacetime.py +++ b/virtual_ship/spacetime.py @@ -1,8 +1,7 @@ """Location class. See class description.""" from dataclasses import dataclass - -import numpy as np +from datetime import datetime from .location import Location @@ -13,4 +12,4 @@ class Spacetime: """A location and time.""" location: Location - time: np.datetime64 + time: datetime From d09b368b724fc842e12b9769874d47c22ab8c35b Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Thu, 6 Jun 2024 12:41:22 +0200 Subject: [PATCH 10/14] use builtin tmpdir pytest --- tests/conftest.py | 28 +------------------ tests/instruments/test_ship_underwater_st.py | 10 +++---- .../instruments/ship_underwater_st.py | 7 +++-- 3 files changed, 10 insertions(+), 35 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index dfeb93b7..1159768d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,14 +1,10 @@ """Test configuration that is ran for every test.""" -import shutil -import tempfile -from typing import Callable, Generator - import pytest @pytest.fixture(autouse=True) -def change_test_dir(request, monkeypatch): +def test_in_working_dir(request, monkeypatch): """ Set the working directory for each test to the directory of that test. @@ -16,25 +12,3 @@ def change_test_dir(request, monkeypatch): :param monkeypatch: - """ monkeypatch.chdir(request.fspath.dirname) - - -@pytest.fixture -def tmp_dir_factory() -> Generator[Callable[[str], str], None, None]: - """ - Yield functions that can generate a random directory and returns its path. - - Created directories are automatically deleted after the test. - - :yields: A generator for temporary directories that will be automatically removed. - """ - created_dirs = [] - - def _create_temp_dir(suffix: str): - dir_path = tempfile.mkdtemp(suffix=suffix) - created_dirs.append(dir_path) - return dir_path - - yield _create_temp_dir - - for dir_path in created_dirs: - shutil.rmtree(dir_path) diff --git a/tests/instruments/test_ship_underwater_st.py b/tests/instruments/test_ship_underwater_st.py index b1294557..582129b4 100644 --- a/tests/instruments/test_ship_underwater_st.py +++ b/tests/instruments/test_ship_underwater_st.py @@ -1,9 +1,9 @@ """Test the simulation of ship salinity temperature measurements.""" import datetime -from typing import Callable import numpy as np +import py import xarray as xr from parcels import FieldSet @@ -11,7 +11,7 @@ from virtual_ship.instruments.ship_underwater_st import simulate_ship_underwater_st -def test_simulate_ship_underwater_st(tmp_dir_factory: Callable[[str], str]) -> None: +def test_simulate_ship_underwater_st(tmpdir: py.path.LocalPath) -> None: # depth at which the sampling will be done DEPTH = -2 @@ -71,16 +71,16 @@ def test_simulate_ship_underwater_st(tmp_dir_factory: Callable[[str], str]) -> N }, ) - out_file_name = tmp_dir_factory(suffix=".zarr") + out_path = tmpdir.join("out.zarr") simulate_ship_underwater_st( fieldset=fieldset, - out_file_name=out_file_name, + out_path=out_path, depth=DEPTH, sample_points=sample_points, ) - results = xr.open_zarr(out_file_name) + results = xr.open_zarr(out_path) # below we assert if output makes sense assert len(results.trajectory) == 1 # expect a singe trajectory diff --git a/virtual_ship/instruments/ship_underwater_st.py b/virtual_ship/instruments/ship_underwater_st.py index 323baec0..9d4940d9 100644 --- a/virtual_ship/instruments/ship_underwater_st.py +++ b/virtual_ship/instruments/ship_underwater_st.py @@ -1,6 +1,7 @@ """Ship salinity and temperature.""" import numpy as np +import py from parcels import FieldSet, ParticleSet, ScipyParticle, Variable from ..spacetime import Spacetime @@ -32,7 +33,7 @@ def _sample_temperature(particle, fieldset, time): def simulate_ship_underwater_st( fieldset: FieldSet, - out_file_name: str, + out_path: str | py.path.LocalPath, depth: float, sample_points: list[Spacetime], ) -> None: @@ -40,7 +41,7 @@ def simulate_ship_underwater_st( Use parcels to simulate underway data, measuring salinity and temperature at the given depth along the ship track in a fieldset. :param fieldset: The fieldset to simulate the sampling in. - :param out_file_name: The file to write the results to. + :param out_path: The path to write the results to. :param depth: The depth at which to measure. 0 is water surface, negative is into the water. :param sample_points: The places and times to sample at. """ @@ -57,7 +58,7 @@ def simulate_ship_underwater_st( # define output file for the simulation # the default outputdt is good(infinite), as we want to just want to write at the end of every call to 'execute' - out_file = particleset.ParticleFile(name=out_file_name) + out_file = particleset.ParticleFile(name=out_path) # iterate over each points, manually set lat lon time, then # execute the particle set for one step, performing one set of measurement From ac87bf8c99fb4b4460cc5f23d35595f6b7b859e9 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Mon, 10 Jun 2024 13:58:10 +0200 Subject: [PATCH 11/14] remove unused iteration variable --- tests/instruments/test_ship_underwater_st.py | 6 ++---- virtual_ship/sailship.py | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/instruments/test_ship_underwater_st.py b/tests/instruments/test_ship_underwater_st.py index 582129b4..784ff4cc 100644 --- a/tests/instruments/test_ship_underwater_st.py +++ b/tests/instruments/test_ship_underwater_st.py @@ -90,13 +90,11 @@ def test_simulate_ship_underwater_st(tmpdir: py.path.LocalPath) -> None: ) # expect as many obs as sample points # for every obs, check if the variables match the expected observations - for i, (obs_i, exp) in enumerate( - zip(results.sel(trajectory=traj).obs, expected_obs, strict=True) - ): + for obs_i, exp in zip(results.sel(trajectory=traj).obs, expected_obs, strict=True): obs = results.sel(trajectory=traj, obs=obs_i) for var in variables: obs_value = obs[var].values.item() exp_value = exp[var] assert np.isclose( obs_value, exp_value - ), f"Observation incorrect {i=} {var=} {obs_value=} {exp_value=}." + ), f"Observation incorrect {obs_i=} {var=} {obs_value=} {exp_value=}." diff --git a/virtual_ship/sailship.py b/virtual_ship/sailship.py index 62bf5767..928756c6 100644 --- a/virtual_ship/sailship.py +++ b/virtual_ship/sailship.py @@ -195,7 +195,7 @@ def sailship(config: VirtualShipConfiguration): print("Simulating onboard salinity and temperature measurements.") simulate_ship_underwater_st( fieldset=config.ship_underwater_st_fieldset, - out_file_name=os.path.join("results", "ship_underwater_st.zarr"), + out_path=os.path.join("results", "ship_underwater_st.zarr"), depth=-2, sample_points=ship_underwater_sts, ) From dfd52ea086865aa659a5f964163dff492e8293b2 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Mon, 10 Jun 2024 14:01:34 +0200 Subject: [PATCH 12/14] explicitly use outputdt inf --- virtual_ship/instruments/ship_underwater_st.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/virtual_ship/instruments/ship_underwater_st.py b/virtual_ship/instruments/ship_underwater_st.py index 9d4940d9..eeb2fd25 100644 --- a/virtual_ship/instruments/ship_underwater_st.py +++ b/virtual_ship/instruments/ship_underwater_st.py @@ -57,8 +57,8 @@ def simulate_ship_underwater_st( ) # define output file for the simulation - # the default outputdt is good(infinite), as we want to just want to write at the end of every call to 'execute' - out_file = particleset.ParticleFile(name=out_path) + # outputdt set to infinie as we want to just want to write at the end of every call to 'execute' + out_file = particleset.ParticleFile(name=out_path, outputdt=np.inf) # iterate over each points, manually set lat lon time, then # execute the particle set for one step, performing one set of measurement From 8b9855b27ba4fe3688929bb7a977096a3a43c443 Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Mon, 10 Jun 2024 14:01:53 +0200 Subject: [PATCH 13/14] typo --- virtual_ship/instruments/ship_underwater_st.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virtual_ship/instruments/ship_underwater_st.py b/virtual_ship/instruments/ship_underwater_st.py index eeb2fd25..99416c0c 100644 --- a/virtual_ship/instruments/ship_underwater_st.py +++ b/virtual_ship/instruments/ship_underwater_st.py @@ -60,7 +60,7 @@ def simulate_ship_underwater_st( # outputdt set to infinie as we want to just want to write at the end of every call to 'execute' out_file = particleset.ParticleFile(name=out_path, outputdt=np.inf) - # iterate over each points, manually set lat lon time, then + # iterate over each point, manually set lat lon time, then # execute the particle set for one step, performing one set of measurement for point in sample_points: particleset.lon_nextloop[:] = point.location.lon From 1c5c3d2a860e07a69c72abdc096a1cb15a22146b Mon Sep 17 00:00:00 2001 From: Aart Stuurman Date: Mon, 10 Jun 2024 18:13:26 +0200 Subject: [PATCH 14/14] comment --- virtual_ship/instruments/ship_underwater_st.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/virtual_ship/instruments/ship_underwater_st.py b/virtual_ship/instruments/ship_underwater_st.py index 99416c0c..b2226381 100644 --- a/virtual_ship/instruments/ship_underwater_st.py +++ b/virtual_ship/instruments/ship_underwater_st.py @@ -7,8 +7,7 @@ from ..spacetime import Spacetime # we specifically use ScipyParticle because we have many small calls to execute -# JITParticle would require compilation every time -# this ends up being faster +# there is some overhead with JITParticle and this ends up being significantly faster _ShipSTParticle = ScipyParticle.add_variables( [ Variable("salinity", dtype=np.float32, initial=np.nan),