From a57ce54ae939c524aa26b51c80e33a8027a7bf38 Mon Sep 17 00:00:00 2001 From: Nicolas Raillard Date: Tue, 30 Jun 2026 10:02:34 +0200 Subject: [PATCH 1/9] Update CICD --- .github/workflows/publish-to-pypi.yml | 92 +++++++++++++++------------ .gitignore | 10 ++- 2 files changed, 60 insertions(+), 42 deletions(-) diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index b71be67..04dc561 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -1,8 +1,11 @@ -name: Publish Resourcecode 📦 to PyPI and TestPypi (for providing test builds to alpha users) +name: Publish Resourcecode 📦 to PyPI on: - pull_request: push: + branches: + - main + tags: + - 'v*' # Déclenche uniquement pour les tags commençant par 'v' jobs: build: @@ -12,9 +15,9 @@ jobs: steps: - uses: actions/checkout@v6 - name: Install uv - uses: astral-sh/setup-uv@v8.1.0 + uses: astral-sh/setup-uv@v8.2.0 with: - version: "0.11.16" + version: "" enable-cache: true - name: Build a binary wheel and a source tarball run: uv build @@ -24,12 +27,50 @@ jobs: name: resourcecode path: dist/ + verify-version: + name: Verify package version + needs: build + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: resourcecode + path: dist/ + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Install package in development mode + run: pip install -e . + - name: Verify version consistency + run: | + # Extraire la version du package installé + PACKAGE_VERSION=$(python -c "import resourcecode; print(resourcecode.__version__.__version__)") + TAG_VERSION=${GITHUB_REF#refs/tags/v} + + echo "Tag version: $TAG_VERSION" + echo "Package version: $PACKAGE_VERSION" + + if [ "$TAG_VERSION" != "$PACKAGE_VERSION" ]; then + echo "ERROR: Version mismatch between tag ($TAG_VERSION) and package ($PACKAGE_VERSION)" + exit 1 + fi + + # Vérifie que la version existe déjà sur PyPI + EXISTS=$(curl -s -o /dev/null -w "%{http_code}" "https://pypi.org/pypi/resourcecode/$TAG_VERSION/json") + if [ "$EXISTS" -ne 404 ]; then + echo "ERROR: Version $TAG_VERSION already exists on PyPI" + exit 1 + fi + publish-to-pypi: - name: >- - Publish Resourcecode 📦 to PyPI - if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes + name: Publish Resourcecode 📦 to PyPI needs: - build + - verify-version runs-on: ubuntu-latest environment: name: publish @@ -51,9 +92,7 @@ jobs: run: uv publish github-release: - name: >- - Sign the Resourcecode 📦 with Sigstore - and upload them to GitHub Release + name: Sign the Resourcecode 📦 with Sigstore and upload to GitHub Release needs: - publish-to-pypi runs-on: ubuntu-latest @@ -82,41 +121,12 @@ jobs: gh release create '${{ github.ref_name }}' --repo '${{ github.repository }}' - --notes "" + --notes "Release ${{ github.ref_name }}" + --generate-notes - name: Upload artifact signatures to GitHub Release env: GITHUB_TOKEN: ${{ github.token }} - # Upload to GitHub Release using the `gh` CLI. - # `dist/` contains the built packages, and the - # sigstore-produced signatures and certificates. run: >- gh release upload '${{ github.ref_name }}' dist/** --repo '${{ github.repository }}' - - publish-to-testpypi: - name: Publish Resourcecode 📦 to TestPyPI - if: "!startsWith(github.ref, 'refs/tags/')" # only publish to testPyPI for non-tagged pushes - needs: - - build - runs-on: ubuntu-latest - - environment: - name: publish - url: https://test.pypi.org/p/resourcecode - - permissions: - id-token: write # IMPORTANT: mandatory for trusted publishing - - steps: - - name: Download all the dists - uses: actions/download-artifact@v4 - with: - name: resourcecode - path: dist/ - - name: Install uv - uses: astral-sh/setup-uv@v8.1.0 - with: - version: "0.11.16" - - name: Publish Resourcecode 📦 to TestPyPI - run: uv publish --publish-url https://test.pypi.org/legacy/ diff --git a/.gitignore b/.gitignore index d40efb6..d33fe4e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,12 @@ uv.lock .venv/ # Jupyter checkpoints -**/.ipynb_checkpoints/ \ No newline at end of file +**/.ipynb_checkpoints/ + +/resourcecode/__pycache__ +/resourcecode/*/__pycache__ +*/__pycache__ +/build +/doc +/.doctrees +/resourcecode.egg-info From 357b1545d41d241b19267bb6e4493f03e8b90621 Mon Sep 17 00:00:00 2001 From: Nicolas Raillard Date: Tue, 30 Jun 2026 10:02:40 +0200 Subject: [PATCH 2/9] Update CHANGELOG.md --- CHANGELOG.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a380d1d..8cddf93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,31 @@ +## Version 2.1 (TBD) + +### 👷 Bug Fixes + - Add Time Zone information in index from `download_data()` + - Updated tests accordingly + +### Packaging & Tooling + - Fix the CI/CD to check before uploading to pypi and remove TestPyPi (unused) + +## Version 2.0 (02/06/2026) + +### Compatibility Updates + - Removed `numexpr` dependency for full pyiodide compatibility + - Updated `np.trapz()` → `np.trapezoid()` (numpy API change) + - Migrated from `scipy.stats.mvn()` to `scipy.stats.multivariate_normal()` + - Fixed crash in `download_data()` with Python 3.14 and numpy/xarray when opening files + - Updated dependency versions for marimo wasm compatibility + +### 👷 Bug Fixes + - Fixed `censgaussfit()` to handle positive semi-definite sigma matrices + - Updated tests to check values instead of `dtypes` + - Set fixed random seed for reproducible tests + +### Packaging & Tooling + - Moved `config.ini` into the package for distribution + - Migrated project to uv package manager + + ## Version 1.3.1 (TBD) ### 👷 Bug fixes - Pin version of Numpy to be < 2.0.0 because `trapz` is renamed in higher versions From c1ceb422a444966bdb1c0814678e16e4c8937e45 Mon Sep 17 00:00:00 2001 From: Nicolas Raillard <61309853+NRaillard@users.noreply.github.com> Date: Tue, 30 Jun 2026 10:09:05 +0200 Subject: [PATCH 3/9] Add TZ (UTC) in output data (#12) * fix:add utc=true to datetime * fix:indentation * Fix: add UTC index in tests * Update output in README.md * Fix: opsplanning is now timezone aware * Fix tests in opsplanning --------- Co-authored-by: Nouhayla BAHADDOU --- README.md | 24 ++++++++++++------------ resourcecode/client.py | 2 +- resourcecode/opsplanning/__init__.py | 13 +++++++------ tests/test_client.py | 18 +++++++++--------- tests/test_opsplanning.py | 1 + 5 files changed, 30 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index acd6e9b..5f123ba 100644 --- a/README.md +++ b/README.md @@ -85,18 +85,18 @@ See the following example: ... """) >>> data - fp hs -2017-01-01 00:00:00 0.412 0.246 -2017-01-01 01:00:00 0.410 0.212 -2017-01-01 02:00:00 0.414 0.172 -2017-01-01 03:00:00 0.437 0.138 -2017-01-01 04:00:00 0.464 0.102 -... ... ... -2017-03-19 02:00:00 0.088 0.056 -2017-03-19 03:00:00 0.088 0.066 -2017-03-19 04:00:00 0.089 0.078 -2017-03-19 05:00:00 0.090 0.084 -2017-03-19 06:00:00 0.732 0.086 + fp hs +2017-01-01 00:00:00+00:00 0.412 0.246 +2017-01-01 01:00:00+00:00 0.410 0.212 +2017-01-01 02:00:00+00:00 0.414 0.172 +2017-01-01 03:00:00+00:00 0.437 0.138 +2017-01-01 04:00:00+00:00 0.464 0.102 +... ... ... +2017-03-19 02:00:00+00:00 0.088 0.056 +2017-03-19 03:00:00+00:00 0.088 0.066 +2017-03-19 04:00:00+00:00 0.089 0.078 +2017-03-19 05:00:00+00:00 0.090 0.084 +2017-03-19 06:00:00+00:00 0.732 0.086 [1855 rows x 2 columns] diff --git a/resourcecode/client.py b/resourcecode/client.py index 81f26fd..f22dc31 100644 --- a/resourcecode/client.py +++ b/resourcecode/client.py @@ -276,7 +276,7 @@ def get_dataframe_from_criteria(self, criteria: Union[str, dict]) -> pd.DataFram return pd.DataFrame( result_array[:, 1:], columns=parsed_criteria["parameter"], - index=pd.to_datetime(index_array.astype(np.int64), unit="ms"), + index=pd.to_datetime(index_array.astype(np.int64), unit="ms", utc=True), ) def _get_rawdata_from_criteria(self, single_parameter_criteria): diff --git a/resourcecode/opsplanning/__init__.py b/resourcecode/opsplanning/__init__.py index 424b6a8..44460ae 100644 --- a/resourcecode/opsplanning/__init__.py +++ b/resourcecode/opsplanning/__init__.py @@ -156,12 +156,12 @@ def oplen_calc(critsubs, oplen, critical_operation=False, date=1, monstrt=True): Nominal length of the operation (if no downtime), in hours critical_operation : BOOLEAN - False for non-critical operations: - With this flag to False, it is assumed that the operation can be - halted for the duration of weather downtime and started again + With this flag to False, it is assumed that the operation can be + halted for the duration of weather downtime and started again - True is for continuous window search: - In this case operations can't be halted and if stopped due to - weather downtime have to restart from the beginning once the - conditions allow + In this case operations can't be halted and if stopped due to + weather downtime have to restart from the beginning once the + conditions allow The default is non critical operation. date : INT,DATETIME optional @@ -207,6 +207,7 @@ def oplen_calc(critsubs, oplen, critical_operation=False, date=1, monstrt=True): start=dt.datetime(yerng[0], morng[0], 1), end=dt.datetime(yerng[1], morng[1], 1), freq="MS", + tz="utc", ) daterng = daterng.shift(dayval - 1, freq="D") oplendetect = pd.Series(np.zeros(daterng.shape[0], dtype="timedelta64[s]"), index=daterng) @@ -215,7 +216,7 @@ def oplen_calc(critsubs, oplen, critical_operation=False, date=1, monstrt=True): daterng = pd.date_range(start=date, end=date) oplendetect = pd.Series(np.zeros(daterng.shape[0], dtype="timedelta64[s]"), index=daterng) else: - msg = "Variable date in single result calculation should be" " a datetime object" + msg = "Variable date in single result calculation should be a datetime object" raise NameError(msg) else: raise NameError("Input option monstrt should be boolean") diff --git a/tests/test_client.py b/tests/test_client.py index 901f74a..7454f2d 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -158,8 +158,8 @@ def test_get_criteria_single_parameter(client): assert len(data) == 744 assert (data.columns == ["fp"]).all() - assert data.index[0] == pd.to_datetime("2017-01-01 00:00:00") - assert data.index[-1] == pd.to_datetime("2017-01-31 23:00:00") + assert data.index[0] == pd.to_datetime("2017-01-01 00:00:00", utc=True) + assert data.index[-1] == pd.to_datetime("2017-01-31 23:00:00", utc=True) assert data.fp[0] == pytest.approx(0.074) assert data.fp[-1] == pytest.approx(0.097) @@ -169,8 +169,8 @@ def test_get_criteria_single_tp_parameter(client): assert len(data) == 744 assert (data.columns == ["tp"]).all() - assert data.index[0] == pd.to_datetime("2017-01-01 00:00:00") - assert data.index[-1] == pd.to_datetime("2017-01-31 23:00:00") + assert data.index[0] == pd.to_datetime("2017-01-01 00:00:00", utc=True) + assert data.index[-1] == pd.to_datetime("2017-01-31 23:00:00", utc=True) assert data.tp[0] == pytest.approx(13.51351) assert data.tp[-1] == pytest.approx(10.30928) @@ -180,8 +180,8 @@ def test_get_criteria_multiple_parameters(client): assert len(data) == 744 assert (data.columns == ["fp", "hs"]).all() - assert data.index[0] == pd.to_datetime("2017-01-01 00:00:00") - assert data.index[-1] == pd.to_datetime("2017-01-31 23:00:00") + assert data.index[0] == pd.to_datetime("2017-01-01 00:00:00", utc=True) + assert data.index[-1] == pd.to_datetime("2017-01-31 23:00:00", utc=True) assert data.fp[0] == pytest.approx(0.074) assert data.fp[-1] == pytest.approx(0.097) assert data.hs[0] == pytest.approx(0.296) @@ -200,7 +200,7 @@ def test_get_criteria_multiple_parameters_and_none_values(client): assert pd.isnull(data.uust[0]) # the third value is not null. - assert data.index[2] == pd.to_datetime("2017-01-01 02:00:00") + assert data.index[2] == pd.to_datetime("2017-01-01 02:00:00", utc=True) assert data.uust[2] == pytest.approx(0.1699999962) data = client.get_dataframe_from_criteria('{"parameter": ["uust", "fp"]}') @@ -210,8 +210,8 @@ def test_get_criteria_multiple_parameters_and_none_values(client): # in that case, we have two variables. The second one, fp, is not null, # therefore the index can be completed despite that uust is null. - assert data.index[0] == pd.to_datetime("2017-01-01 00:00:00") - assert data.index[-1] == pd.to_datetime("2017-01-31 23:00:00") + assert data.index[0] == pd.to_datetime("2017-01-01 00:00:00", utc=True) + assert data.index[-1] == pd.to_datetime("2017-01-31 23:00:00", utc=True) assert data.fp[0] == pytest.approx(0.074) assert data.fp[-1] == pytest.approx(0.097) diff --git a/tests/test_opsplanning.py b/tests/test_opsplanning.py index 7c08fa1..22f3483 100644 --- a/tests/test_opsplanning.py +++ b/tests/test_opsplanning.py @@ -34,6 +34,7 @@ def data(): parse_dates=True, dayfirst=True, ) + df = df.tz_localize("UTC") return df From ea4a357b70c8f62ae3462646ad2701f4c1f55b1f Mon Sep 17 00:00:00 2001 From: Nicolas Raillard Date: Tue, 30 Jun 2026 10:14:47 +0200 Subject: [PATCH 4/9] Update CHANGELOG.md --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cddf93..9157793 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,13 @@ ## Version 2.1 (TBD) ### 👷 Bug Fixes - - Add Time Zone information in index from `download_data()` + - Add Time Zone information in index from `download_data()` (thanks to @Nouhaylaa1) - Updated tests accordingly ### Packaging & Tooling - Fix the CI/CD to check before uploading to pypi and remove TestPyPi (unused) -## Version 2.0 (02/06/2026) +## Version 2.0 (02/06/2026) (thanks to @chabotsi) ### Compatibility Updates - Removed `numexpr` dependency for full pyiodide compatibility From 9ea922b2c79d7275e6ea1b756ace549a38fea0f5 Mon Sep 17 00:00:00 2001 From: Nicolas Raillard Date: Tue, 30 Jun 2026 10:26:29 +0200 Subject: [PATCH 5/9] Update check.yml --- .github/workflows/check.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 50f4c73..695d86c 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -46,7 +46,8 @@ jobs: - "3.11" os: - ubuntu-latest - - macos-13 + - macos-26 + - macos-26-intel - windows-latest env: MPLBACKEND: Agg # https://github.com/orgs/community/discussions/26434 From f33eecf02fc65a9e59acc94310121a241ae987d4 Mon Sep 17 00:00:00 2001 From: Nicolas Raillard Date: Tue, 30 Jun 2026 10:46:32 +0200 Subject: [PATCH 6/9] Update copyright date --- .gitignore | 1 - doc/conf.py | 6 ++- resourcecode/__init__.py | 2 +- resourcecode/__version__.py | 4 +- resourcecode/client.py | 20 ++++++--- resourcecode/data/__init__.py | 2 +- resourcecode/eva/__init__.py | 2 +- resourcecode/eva/censgaussfit.py | 2 +- resourcecode/eva/extrema.py | 2 +- resourcecode/eva/huseby.py | 25 ++++++++--- resourcecode/eva/simulation.py | 2 +- resourcecode/exceptions.py | 2 +- resourcecode/io.py | 10 +++-- resourcecode/opsplanning/__init__.py | 45 +++++++++++++------ resourcecode/plotly_theme.py | 2 +- .../producible_assessment/__init__.py | 2 +- resourcecode/producible_assessment/main.py | 30 +++++++++---- resourcecode/resassess/__init__.py | 15 +++++-- resourcecode/spectrum/__init__.py | 2 +- resourcecode/spectrum/compute_parameters.py | 2 +- resourcecode/spectrum/convert2D1D.py | 6 ++- resourcecode/spectrum/dispersion.py | 2 +- resourcecode/spectrum/download_data.py | 42 ++++++++++++++--- resourcecode/spectrum/jonswap.py | 15 +++++-- resourcecode/spectrum/plots.py | 2 +- resourcecode/utils.py | 2 +- resourcecode/weatherwindow/__init__.py | 2 +- resourcecode/weatherwindow/weatherwindow.py | 2 +- tests/__init__.py | 2 +- tests/test_client.py | 14 ++++-- tests/test_data.py | 2 +- tests/test_eva.py | 2 +- tests/test_netfcd.py | 2 +- tests/test_opsplanning.py | 2 +- tests/test_producible_assessment.py | 2 +- tests/test_resassess.py | 6 ++- tests/test_spectrum.py | 18 +++++--- tests/test_utils.py | 2 +- tests/test_weather_window.py | 14 ++++-- 39 files changed, 224 insertions(+), 93 deletions(-) diff --git a/.gitignore b/.gitignore index d33fe4e..122674c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,5 @@ uv.lock /resourcecode/*/__pycache__ */__pycache__ /build -/doc /.doctrees /resourcecode.egg-info diff --git a/doc/conf.py b/doc/conf.py index 051e530..4d60c47 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. @@ -70,7 +70,9 @@ sphinx_gallery_conf = { "examples_dirs": "../examples", # path to your example scripts "gallery_dirs": "auto_examples", # path to where to save gallery generated output - "within_subsection_order": (sphinx_gallery.sorting.FileNameSortKey), # to sort gallery examples by file name + "within_subsection_order": ( + sphinx_gallery.sorting.FileNameSortKey + ), # to sort gallery examples by file name } # Add any paths that contain templates here, relative to this directory. diff --git a/resourcecode/__init__.py b/resourcecode/__init__.py index bf38226..0ad48dd 100644 --- a/resourcecode/__init__.py +++ b/resourcecode/__init__.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. diff --git a/resourcecode/__version__.py b/resourcecode/__version__.py index bd425de..84faa84 100644 --- a/resourcecode/__version__.py +++ b/resourcecode/__version__.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # This file is part of Resourcecode. @@ -17,5 +17,5 @@ # You should have received a copy of the GNU General Public License along # with Resourcecode. If not, see . -numversion = (2, 0, 0) +numversion = (2, 1, 0) __version__ = ".".join(str(num) for num in numversion) diff --git a/resourcecode/client.py b/resourcecode/client.py index f22dc31..bb5b81a 100644 --- a/resourcecode/client.py +++ b/resourcecode/client.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. @@ -114,7 +114,9 @@ def get_dataframe( return self.get_dataframe_from_criteria(criteria) - def get_dataframe_from_url(self, selection_url: str, parameters: Iterable[str] = ("hs",)) -> pd.DataFrame: + def get_dataframe_from_url( + self, selection_url: str, parameters: Iterable[str] = ("hs",) + ) -> pd.DataFrame: """Get the pandas dataframe of the data described by the url Parameters @@ -212,10 +214,14 @@ def get_dataframe_from_criteria(self, criteria: Union[str, dict]) -> pd.DataFram try: node_id = int(parsed_criteria["node"]) except ValueError: # failed to convert node to an integer - raise BadPointIdError("Point Id must be an integer, can not be " f"{parsed_criteria['node']!r}") + raise BadPointIdError( + f"Point Id must be an integer, can not be {parsed_criteria['node']!r}" + ) else: if node_id not in self.possible_points_id: - raise BadPointIdError(f"{parsed_criteria['node']} is an unknown pointId.") + raise BadPointIdError( + f"{parsed_criteria['node']} is an unknown pointId." + ) # Cassandra database start indexing at 1, so decrement node parsed_criteria["node"] = parsed_criteria["node"] - 1 @@ -298,4 +304,8 @@ def _get_rawdata_from_criteria(self, single_parameter_criteria): if response.ok: return response.json() - raise ValueError("Unable to get a response from the database" "(status code = {})".format(response.status_code)) + raise ValueError( + "Unable to get a response from the database(status code = {})".format( + response.status_code + ) + ) diff --git a/resourcecode/data/__init__.py b/resourcecode/data/__init__.py index fcd8c70..ad83c61 100644 --- a/resourcecode/data/__init__.py +++ b/resourcecode/data/__init__.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. diff --git a/resourcecode/eva/__init__.py b/resourcecode/eva/__init__.py index ee4b3cf..d5fdece 100644 --- a/resourcecode/eva/__init__.py +++ b/resourcecode/eva/__init__.py @@ -1,7 +1,7 @@ # coding: utf-8 # Extreme Values Modelling -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. diff --git a/resourcecode/eva/censgaussfit.py b/resourcecode/eva/censgaussfit.py index 62a5fb0..8bb0787 100644 --- a/resourcecode/eva/censgaussfit.py +++ b/resourcecode/eva/censgaussfit.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. diff --git a/resourcecode/eva/extrema.py b/resourcecode/eva/extrema.py index 282cf1d..9958717 100644 --- a/resourcecode/eva/extrema.py +++ b/resourcecode/eva/extrema.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. diff --git a/resourcecode/eva/huseby.py b/resourcecode/eva/huseby.py index 344ef5a..c4a130b 100644 --- a/resourcecode/eva/huseby.py +++ b/resourcecode/eva/huseby.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. @@ -79,7 +79,10 @@ def huseby(X: np.ndarray, prob: np.ndarray, ntheta: int): Y.sort() C[i, :] = (Y[kc] - Y[kf]) * dk + Y[kf] - ps = ctheta[:, np.newaxis] @ ctheta[:, np.newaxis].T + stheta[:, np.newaxis] @ stheta[:, np.newaxis].T + ps = ( + ctheta[:, np.newaxis] @ ctheta[:, np.newaxis].T + + stheta[:, np.newaxis] @ stheta[:, np.newaxis].T + ) ps[ps < 0] = 0 for iprob in range(len(prob)): @@ -96,7 +99,9 @@ def huseby(X: np.ndarray, prob: np.ndarray, ntheta: int): if M == 3: C = np.ones((ntheta, ntheta, len(prob)), dtype=float) - indj = np.hstack((np.arange(0, ntheta / 4 + 1), ntheta - np.arange(1, ntheta / 4 + 1))).astype(int) + indj = np.hstack( + (np.arange(0, ntheta / 4 + 1), ntheta - np.arange(1, ntheta / 4 + 1)) + ).astype(int) nindj = ntheta / 2 + 1 ncdir = int(ntheta * nindj) cdir = np.zeros((ncdir, M)) @@ -115,8 +120,12 @@ def huseby(X: np.ndarray, prob: np.ndarray, ntheta: int): ps[ps < 0] = 0 a = np.arange(ntheta / 4 + 1, ntheta - ntheta / 4).astype(int) - b = np.hstack((np.arange(ntheta / 2, ntheta), np.arange(0, ntheta / 2))).astype(int) - c = np.hstack((np.arange(ntheta / 4 - 1, -1, -1), ntheta - np.arange(1, ntheta / 4))).astype(int) + b = np.hstack((np.arange(ntheta / 2, ntheta), np.arange(0, ntheta / 2))).astype( + int + ) + c = np.hstack( + (np.arange(ntheta / 4 - 1, -1, -1), ntheta - np.arange(1, ntheta / 4)) + ).astype(int) d = ctheta[:, np.newaxis] @ ctheta[np.newaxis, indj] e = np.repeat(stheta[indj], ntheta).reshape((-1, ntheta)).T f = stheta[:, np.newaxis] @ ctheta[np.newaxis, indj] @@ -150,7 +159,11 @@ def huseby(X: np.ndarray, prob: np.ndarray, ntheta: int): + X_mean[2] ) - Yres[:, iprob] = L[0, 1] * X_std[1] * Xres[:, iprob] + L[1, 1] * X_std[1] * Yres[:, iprob] + X_mean[1] + Yres[:, iprob] = ( + L[0, 1] * X_std[1] * Xres[:, iprob] + + L[1, 1] * X_std[1] * Yres[:, iprob] + + X_mean[1] + ) Xres[:, iprob] = L[0, 0] * X_std[0] * Xres[:, iprob] + X_mean[0] diff --git a/resourcecode/eva/simulation.py b/resourcecode/eva/simulation.py index d2a1482..5f3b471 100644 --- a/resourcecode/eva/simulation.py +++ b/resourcecode/eva/simulation.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. diff --git a/resourcecode/exceptions.py b/resourcecode/exceptions.py index 64cb055..70244a7 100644 --- a/resourcecode/exceptions.py +++ b/resourcecode/exceptions.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. diff --git a/resourcecode/io.py b/resourcecode/io.py index 293f064..fde697b 100644 --- a/resourcecode/io.py +++ b/resourcecode/io.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. @@ -38,7 +38,9 @@ NETCFD_DESCRIPTION = json.load(fobj) -def to_netcdf(dataframe: pd.DataFrame, path: Union[str, Path, None] = None) -> Union[bytes, "Delayed", None]: +def to_netcdf( + dataframe: pd.DataFrame, path: Union[str, Path, None] = None +) -> Union[bytes, "Delayed", None]: """Write dataframe contents to a netCFD file. Parameters @@ -82,7 +84,9 @@ def to_mat( """ - df = dataframe.reset_index(names="time") # convert the pandas index to a proper variable + df = dataframe.reset_index( + names="time" + ) # convert the pandas index to a proper variable df.time = 719529 + pd.to_numeric(df.time) / ( 3600 * 1e9 * 24 ) # 1970-01-01 + time in fractional days from nanoseconds diff --git a/resourcecode/opsplanning/__init__.py b/resourcecode/opsplanning/__init__.py index 44460ae..16351f5 100644 --- a/resourcecode/opsplanning/__init__.py +++ b/resourcecode/opsplanning/__init__.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. @@ -44,13 +44,13 @@ The module, in order of execution to produce a full result, consists of: 1. Use a pandas query to limit the dataset to the timestamps matching - operational criteria (`data.query("hs < 2 and tp < 3")` for instance) + operational criteria (`data.query("hs < 2 and tp < 3")` for instance) 2. ww_calc - method to identify the weather windows 3. oplen_calc - method to calculate operational length 4. wwmonstats - method that produces monthly statistics of number of - weather windows + weather windows 5. olmonstats - method that produces monthly statistics of operational - length values + length values Created on Wed Dec 2 14:14:48 2020 @@ -93,7 +93,9 @@ def _get_timestep(data: pd.DataFrame) -> pd.Timedelta: return tstep -def ww_calc(critsubs: pd.DataFrame, winlen: float, concurrent_windows: bool = True) -> pd.Series: +def ww_calc( + critsubs: pd.DataFrame, winlen: float, concurrent_windows: bool = True +) -> pd.Series: """ Method that calculates and returns start date of each weather window @@ -126,7 +128,9 @@ def ww_calc(critsubs: pd.DataFrame, winlen: float, concurrent_windows: bool = Tr strtwin = critsubs.index.values[k] thistime = strtwin nexttime = critsubs.index.values[k + 1] - while pd.Timedelta(nexttime - thistime) <= tstep and k < (critsubs.shape[0] - 2): + while pd.Timedelta(nexttime - thistime) <= tstep and k < ( + critsubs.shape[0] - 2 + ): k += 1 if pd.Timedelta(nexttime - strtwin) >= dt.timedelta(seconds=3600 * winlen): windetect.append(strtwin) @@ -199,7 +203,10 @@ def oplen_calc(critsubs, oplen, critical_operation=False, date=1, monstrt=True): elif isinstance(date, int) and date >= 1: dayval = date else: - msg = "Variable date in monthly results calculation should be" " positive integer or datetime object" + msg = ( + "Variable date in monthly results calculation should be" + " positive integer or datetime object" + ) raise NameError(msg) yerng = [min(critsubs.index).year, max(critsubs.index).year] morng = [min(critsubs.index).month, max(critsubs.index).month] @@ -210,13 +217,19 @@ def oplen_calc(critsubs, oplen, critical_operation=False, date=1, monstrt=True): tz="utc", ) daterng = daterng.shift(dayval - 1, freq="D") - oplendetect = pd.Series(np.zeros(daterng.shape[0], dtype="timedelta64[s]"), index=daterng) + oplendetect = pd.Series( + np.zeros(daterng.shape[0], dtype="timedelta64[s]"), index=daterng + ) elif monstrt is False: if isinstance(date, dt.datetime): daterng = pd.date_range(start=date, end=date) - oplendetect = pd.Series(np.zeros(daterng.shape[0], dtype="timedelta64[s]"), index=daterng) + oplendetect = pd.Series( + np.zeros(daterng.shape[0], dtype="timedelta64[s]"), index=daterng + ) else: - msg = "Variable date in single result calculation should be a datetime object" + msg = ( + "Variable date in single result calculation should be a datetime object" + ) raise NameError(msg) else: raise NameError("Input option monstrt should be boolean") @@ -272,7 +285,9 @@ def wwmonstats(windetect): yeun = windetect.dt.year.unique() moun = windetect.dt.month.unique() yeun.sort() - wwmonres = pd.DataFrame(np.zeros([yeun.shape[0], moun.shape[0]]), columns=moun, index=yeun) + wwmonres = pd.DataFrame( + np.zeros([yeun.shape[0], moun.shape[0]]), columns=moun, index=yeun + ) for ye, mo in product(yeun, moun): subs = windetect[(windetect.dt.year == ye) & (windetect.dt.month == mo)] @@ -304,8 +319,12 @@ def olmonstats(oplendetect): moun.sort() yeun.sort() - olmonres = pd.DataFrame(np.zeros([yeun.shape[0], moun.shape[0]]), columns=moun, index=yeun) + olmonres = pd.DataFrame( + np.zeros([yeun.shape[0], moun.shape[0]]), columns=moun, index=yeun + ) for ye, mo in product(yeun, moun): - subs = oplendetect[(oplendetect.index.year == ye) & (oplendetect.index.month == mo)] + subs = oplendetect[ + (oplendetect.index.year == ye) & (oplendetect.index.month == mo) + ] olmonres.at[ye, mo] = subs[0].days * 24 + subs[0].seconds / 3600 return olmonres diff --git a/resourcecode/plotly_theme.py b/resourcecode/plotly_theme.py index 9953267..b29cc4b 100644 --- a/resourcecode/plotly_theme.py +++ b/resourcecode/plotly_theme.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. diff --git a/resourcecode/producible_assessment/__init__.py b/resourcecode/producible_assessment/__init__.py index 97fcb41..8902031 100644 --- a/resourcecode/producible_assessment/__init__.py +++ b/resourcecode/producible_assessment/__init__.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. diff --git a/resourcecode/producible_assessment/main.py b/resourcecode/producible_assessment/main.py index 4b800e7..7b97cc8 100644 --- a/resourcecode/producible_assessment/main.py +++ b/resourcecode/producible_assessment/main.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. @@ -40,19 +40,25 @@ def __init__(self, capture_width, s): self.times = s.index # time vector self.freqs = s.columns # frequency vector # time domain data - self.power = pd.DataFrame(np.zeros(len(self.times)), index=self.times) # absorbed power (W) + self.power = pd.DataFrame( + np.zeros(len(self.times)), index=self.times + ) # absorbed power (W) # absorbed power, no reduction (W) self.power_no_red = pd.DataFrame(np.zeros(len(self.times)), index=self.times) self.mean_power = None # mean absorbed power (W) self.mean_power_no_red = None # mean absorbed power, no reduction (W) self.median_power = None # median absorbed power (W) self.median_power_no_red = None # median absorbed power, no reduction (W) - self.pto_damp = pd.DataFrame(np.zeros(len(self.times)), index=self.times) # PTO damping (Ns/m) + self.pto_damp = pd.DataFrame( + np.zeros(len(self.times)), index=self.times + ) # PTO damping (Ns/m) # PTO damping, no reduction (Ns/m) self.pto_damp_no_red = pd.DataFrame(np.zeros(len(self.times)), index=self.times) self.cumulative_power = None # cumulative power (W) # frequency domain data - self.freq_data = None # contains Hs, Tp, absorbed power and PTO damping in frequency domain + self.freq_data = ( + None # contains Hs, Tp, absorbed power and PTO damping in frequency domain + ) # interpolate frequencies (if needed) to match sea-state and PTO capture width data self.interp_freq() # power computation @@ -89,7 +95,9 @@ def interp_freq(self): continue known_freqs.add(freq) - self.capture_width = self.capture_width.append(pd.Series(name=freq, dtype="float64")) + self.capture_width = self.capture_width.append( + pd.Series(name=freq, dtype="float64") + ) self.capture_width.sort_index(inplace=True) self.capture_width = self.capture_width.interpolate() @@ -168,12 +176,16 @@ def get_power_pto_damp(self): "PTO damping": self.pto_damp.values.flatten(), } ) - self.mean_power = pd.DataFrame(data=self.power.mean()[0] * np.ones(len(self.times)), index=self.times) + self.mean_power = pd.DataFrame( + data=self.power.mean()[0] * np.ones(len(self.times)), index=self.times + ) self.mean_power_no_red = pd.DataFrame( data=self.power_no_red.mean()[0] * np.ones(len(self.times)), index=self.times, ) - self.median_power = pd.DataFrame(data=self.power.median()[0] * np.ones(len(self.times)), index=self.times) + self.median_power = pd.DataFrame( + data=self.power.median()[0] * np.ones(len(self.times)), index=self.times + ) self.median_power_no_red = pd.DataFrame( data=self.power_no_red.median()[0] * np.ones(len(self.times)), index=self.times, @@ -183,7 +195,9 @@ def get_cumulative_power(self): """Compute PTO cumulative power""" power_ordered = self.power.sort_values(by=0) - self.cumulative_power = pd.DataFrame(data=100 * power_ordered.cumsum() / power_ordered.sum()) + self.cumulative_power = pd.DataFrame( + data=100 * power_ordered.cumsum() / power_ordered.sum() + ) @staticmethod def compute_spectrum_moment(f, s, n=0): diff --git a/resourcecode/resassess/__init__.py b/resourcecode/resassess/__init__.py index 895f9ca..cb672fb 100644 --- a/resourcecode/resassess/__init__.py +++ b/resourcecode/resassess/__init__.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. @@ -71,7 +71,9 @@ def exceed(df: pd.DataFrame) -> Tuple[pd.DataFrame, np.ndarray]: return datasrt, exceedance -def univar_monstats(df: pd.DataFrame, varnm: str) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]: +def univar_monstats( + df: pd.DataFrame, varnm: str +) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]: """ Method to calculate univariate statistics for any input variable. Used in resource assessment to produce deliverables as per IEC @@ -99,7 +101,10 @@ def univar_monstats(df: pd.DataFrame, varnm: str) -> Tuple[pd.DataFrame, pd.Data """ if varnm not in df.columns: existing_col = ", ".join(sorted(df.columns)) - raise NameError(f"Parameter {varnm} is not in the dataframe. " f"Possible values are: {existing_col}") + raise NameError( + f"Parameter {varnm} is not in the dataframe. " + f"Possible values are: {existing_col}" + ) # sort rows according to the variable selected sorted_df = df[[varnm]].sort_values(by=varnm) sorted_df.index = pd.to_datetime(df.index, format="%d/%m/%Y %H:%M") @@ -108,7 +113,9 @@ def univar_monstats(df: pd.DataFrame, varnm: str) -> Tuple[pd.DataFrame, pd.Data sorted_df["month_index"] = sorted_df.index.month sorted_df["month"] = sorted_df.index.month_name() sorted_df["year"] = sorted_df.index.year - sorted_df["Exceedance"] = sorted_df.groupby("month")[varnm].rank(method="min", pct=True) + sorted_df["Exceedance"] = sorted_df.groupby("month")[varnm].rank( + method="min", pct=True + ) res = [sorted_df] diff --git a/resourcecode/spectrum/__init__.py b/resourcecode/spectrum/__init__.py index 1d982ac..b52c020 100644 --- a/resourcecode/spectrum/__init__.py +++ b/resourcecode/spectrum/__init__.py @@ -1,7 +1,7 @@ # coding: utf-8 # Spectral data tools -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. diff --git a/resourcecode/spectrum/compute_parameters.py b/resourcecode/spectrum/compute_parameters.py index 19ae022..4776844 100644 --- a/resourcecode/spectrum/compute_parameters.py +++ b/resourcecode/spectrum/compute_parameters.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. diff --git a/resourcecode/spectrum/convert2D1D.py b/resourcecode/spectrum/convert2D1D.py index 893df16..45b3c7d 100644 --- a/resourcecode/spectrum/convert2D1D.py +++ b/resourcecode/spectrum/convert2D1D.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. @@ -22,7 +22,9 @@ import xarray -def raw_convert_spectrum_2Dto1D(spectrum_2D: np.ndarray, vdir: np.ndarray) -> np.ndarray: +def raw_convert_spectrum_2Dto1D( + spectrum_2D: np.ndarray, vdir: np.ndarray +) -> np.ndarray: """ Converts the 2D spectrum to a 1D spectrum diff --git a/resourcecode/spectrum/dispersion.py b/resourcecode/spectrum/dispersion.py index cfabe8f..e9c7d59 100644 --- a/resourcecode/spectrum/dispersion.py +++ b/resourcecode/spectrum/dispersion.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. diff --git a/resourcecode/spectrum/download_data.py b/resourcecode/spectrum/download_data.py index 13ae3ef..e405efb 100644 --- a/resourcecode/spectrum/download_data.py +++ b/resourcecode/spectrum/download_data.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. @@ -54,12 +54,26 @@ def download_single_2D_file( A dataset object with the data read from the downloaded netCDF file. """ base = "ftp://ftp.ifremer.fr/ifremer/dataref/ww3/resourcecode/HINDCAST/" - url = base + year + "/" + month + "/SPEC_NC/RSCD_WW3-RSCD-UG-" + point + "_" + year + month + "_spec.nc" + url = ( + base + + year + + "/" + + month + + "/SPEC_NC/RSCD_WW3-RSCD-UG-" + + point + + "_" + + year + + month + + "_spec.nc" + ) if point not in set(get_grid_spec().name): raise ValueError(f"{point} is an unkown location") - if int(year) < get_covered_period()["start"].year or int(year) > get_covered_period()["end"].year: + if ( + int(year) < get_covered_period()["start"].year + or int(year) > get_covered_period()["end"].year + ): raise ValueError(f"{year} is outsite the covered period") if int(month) < 1 or int(month) > 12: @@ -111,16 +125,32 @@ def download_single_1D_file( A dataset object with the data read from the downloaded netCDF file. """ base = "ftp://ftp.ifremer.fr/ifremer/dataref/ww3/resourcecode/HINDCAST/" - url = base + year + "/" + month + "/FREQ_NC/RSCD_WW3-RSCD-UG-" + point + "_" + year + month + "_freq.nc" + url = ( + base + + year + + "/" + + month + + "/FREQ_NC/RSCD_WW3-RSCD-UG-" + + point + + "_" + + year + + month + + "_freq.nc" + ) if point not in set(get_grid_spec().name): raise ValueError(f"{point} is an unkown location") - if int(year) < get_covered_period()["start"].year or int(year) > get_covered_period()["end"].year: + if ( + int(year) < get_covered_period()["start"].year + or int(year) > get_covered_period()["end"].year + ): raise ValueError(f"{year} is outsite the covered period") if int(month) < 1 or int(month) > 12: - raise ValueError(f"{month} must by between 1 and 12 with a leading zero if needed.") + raise ValueError( + f"{month} must by between 1 and 12 with a leading zero if needed." + ) with contextlib.closing(urllib.request.urlopen(url)) as response: with tempfile.NamedTemporaryFile(delete=False, suffix=".nc") as tmp_file: diff --git a/resourcecode/spectrum/jonswap.py b/resourcecode/spectrum/jonswap.py index b14fb40..dfb1306 100644 --- a/resourcecode/spectrum/jonswap.py +++ b/resourcecode/spectrum/jonswap.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. @@ -50,13 +50,22 @@ def jonswap(hs: float, tp: float, gamma: float, freq: np.ndarray) -> np.ndarray: * (hs**2) / (tp**4) * np.exp(-5.0 / (4 * tp**4) / (freq**4)) - * gamma ** (np.exp(-((freq - 1 / tp) ** 2) * (tp**2) / (2 * (np.where(freq < (1.0 / tp), 0.07, 0.09) ** 2)))) + * gamma + ** ( + np.exp( + -((freq - 1 / tp) ** 2) + * (tp**2) + / (2 * (np.where(freq < (1.0 / tp), 0.07, 0.09) ** 2)) + ) + ) ) alpha = (hs**2) / (16 * np.trapezoid(sf, x=freq)) return alpha * sf -def compute_jonswap_wave_spectrum(seastate_data: pd.DataFrame, freq: np.ndarray, gamma: float = 1) -> pd.DataFrame: +def compute_jonswap_wave_spectrum( + seastate_data: pd.DataFrame, freq: np.ndarray, gamma: float = 1 +) -> pd.DataFrame: """Computes JONSWAP wave spectrum time series from Hs and Tp time series Parameters diff --git a/resourcecode/spectrum/plots.py b/resourcecode/spectrum/plots.py index 3b1cc92..209314d 100644 --- a/resourcecode/spectrum/plots.py +++ b/resourcecode/spectrum/plots.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. diff --git a/resourcecode/utils.py b/resourcecode/utils.py index 2f4af0e..69777b0 100644 --- a/resourcecode/utils.py +++ b/resourcecode/utils.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. diff --git a/resourcecode/weatherwindow/__init__.py b/resourcecode/weatherwindow/__init__.py index dd51aa1..a7a8ad5 100644 --- a/resourcecode/weatherwindow/__init__.py +++ b/resourcecode/weatherwindow/__init__.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. diff --git a/resourcecode/weatherwindow/weatherwindow.py b/resourcecode/weatherwindow/weatherwindow.py index 1bf92cb..3713ac8 100644 --- a/resourcecode/weatherwindow/weatherwindow.py +++ b/resourcecode/weatherwindow/weatherwindow.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. diff --git a/tests/__init__.py b/tests/__init__.py index 886a1b2..f17047f 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. diff --git a/tests/test_client.py b/tests/test_client.py index 7454f2d..95b0bb5 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. @@ -60,7 +60,9 @@ def mock_requests_get_raw_data(query_url, parameters): for date, value in data["result"]["data"]: # start_date and end_date must be multiplied by 1e3, because # cassandra returns milliseconds - if date is not None and (start_date is not None and date < start_date * 1e3): + if date is not None and ( + start_date is not None and date < start_date * 1e3 + ): continue if date and end_date is not None and date > end_date * 1e3: @@ -137,7 +139,9 @@ def test_get_raw_data(): parameter = "fp" client = resourcecode.Client() - with mock.patch("requests.get", side_effect=mock_requests_get_raw_data) as mock_requests_get: + with mock.patch( + "requests.get", side_effect=mock_requests_get_raw_data + ) as mock_requests_get: json_data = client._get_rawdata_from_criteria( { "parameter": [ @@ -146,7 +150,9 @@ def test_get_raw_data(): } ) - mock_requests_get.assert_called_once_with(client.cassandra_base_url + "api/timeseries", {"parameter": [parameter]}) + mock_requests_get.assert_called_once_with( + client.cassandra_base_url + "api/timeseries", {"parameter": [parameter]} + ) assert json_data["query"]["parameterCode"] == parameter dataset_size = json_data["result"]["dataSetSize"] diff --git a/tests/test_data.py b/tests/test_data.py index 32c26c8..ea9e257 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. diff --git a/tests/test_eva.py b/tests/test_eva.py index 3919fba..6aba533 100644 --- a/tests/test_eva.py +++ b/tests/test_eva.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. diff --git a/tests/test_netfcd.py b/tests/test_netfcd.py index 74e3b38..7e736e8 100644 --- a/tests/test_netfcd.py +++ b/tests/test_netfcd.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. diff --git a/tests/test_opsplanning.py b/tests/test_opsplanning.py index 22f3483..abc44da 100644 --- a/tests/test_opsplanning.py +++ b/tests/test_opsplanning.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. diff --git a/tests/test_producible_assessment.py b/tests/test_producible_assessment.py index 70c824b..26af1e2 100644 --- a/tests/test_producible_assessment.py +++ b/tests/test_producible_assessment.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. diff --git a/tests/test_resassess.py b/tests/test_resassess.py index fca8a99..322ed2b 100644 --- a/tests/test_resassess.py +++ b/tests/test_resassess.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. @@ -85,7 +85,9 @@ def test_univar_monstats(data): stored_results = ("monthly_stat.csv", "yearly_stat.csv") for result, expected_result_path in zip((dtm, dty), stored_results): path = DATA_DIR / "resassess" / expected_result_path - expected_result = pd.read_csv(path, index_col=0, dtype={"month_index": np.int32}) + expected_result = pd.read_csv( + path, index_col=0, dtype={"month_index": np.int32} + ) # count is the first column return by describe count_index = result.columns.get_loc("count") diff --git a/tests/test_spectrum.py b/tests/test_spectrum.py index 0947c71..f6383fd 100644 --- a/tests/test_spectrum.py +++ b/tests/test_spectrum.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. @@ -51,7 +51,9 @@ def test_compute_parameter_1D(): depth = float(np.loadtxt(DATA_DIR / "spectrum" / "depth.csv", delimiter=",")) etfh = np.loadtxt(DATA_DIR / "spectrum" / "Etfh.csv", delimiter=",") - expected_parameters = SeaStatesParameters(*np.loadtxt(DATA_DIR / "spectrum" / "parameters_1D.csv", delimiter=",")) + expected_parameters = SeaStatesParameters( + *np.loadtxt(DATA_DIR / "spectrum" / "parameters_1D.csv", delimiter=",") + ) got_parameters = raw_compute_parameters_from_1D_spectrum(etfh, freq, depth) assert got_parameters.approx(expected_parameters) @@ -63,7 +65,9 @@ def test_compute_parameter_2D(): vdir = np.loadtxt(DATA_DIR / "spectrum" / "dir.csv", delimiter=",") depth = float(np.loadtxt(DATA_DIR / "spectrum" / "depth.csv", delimiter=",")) - expected_parameters = SeaStatesParameters(*np.loadtxt(DATA_DIR / "spectrum" / "parameters_2D.csv", delimiter=",")) + expected_parameters = SeaStatesParameters( + *np.loadtxt(DATA_DIR / "spectrum" / "parameters_2D.csv", delimiter=",") + ) got_parameters = raw_compute_parameters_from_2D_spectrum(spec, freq, vdir, depth) assert got_parameters.approx(expected_parameters) @@ -84,7 +88,9 @@ def test_download_2D_file(): scale_factor = expected_spectrum["efth"].encoding.get("scale_factor", 1.0) add_offset = expected_spectrum["efth"].encoding.get("add_offset", 0.0) efth_decoded = efth_encoded * scale_factor + add_offset - expected_spectrum = expected_spectrum.assign(Ef=np.power(10.0, efth_decoded) - 1e-12) + expected_spectrum = expected_spectrum.assign( + Ef=np.power(10.0, efth_decoded) - 1e-12 + ) expected_spectrum = expected_spectrum.drop_vars("efth") got_spectrum = get_2D_spectrum("W001933N55743", ["2016"], ["05"]) @@ -93,7 +99,9 @@ def test_download_2D_file(): def test_download_1D_file(): - expected_spectrum = xarray.open_dataset(DATA_DIR / "spectrum" / "RSCD_WW3-RSCD-UG-W001933N55743_201605_freq.nc") + expected_spectrum = xarray.open_dataset( + DATA_DIR / "spectrum" / "RSCD_WW3-RSCD-UG-W001933N55743_201605_freq.nc" + ) expected_spectrum = expected_spectrum.drop_dims("string40").squeeze() expected_spectrum = expected_spectrum.drop_vars(["station"]) diff --git a/tests/test_utils.py b/tests/test_utils.py index bdc01a2..d4ce105 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. diff --git a/tests/test_weather_window.py b/tests/test_weather_window.py index b9ed74d..6f4bebd 100644 --- a/tests/test_weather_window.py +++ b/tests/test_weather_window.py @@ -1,6 +1,6 @@ # coding: utf-8 -# Copyright 2020-2022 IFREMER (Brest, FRANCE), all rights reserved. +# Copyright 2020-2026 IFREMER (Brest, FRANCE), all rights reserved. # contact -- mailto:nicolas.raillard@ifremer.fr # # This file is part of Resourcecode. @@ -82,6 +82,12 @@ def test_weather_windows(hs): result = compute_weather_windows(hs, month) assert result.PT.mean() == pytest.approx(expected_month_stats["PT_mean"]) - assert result.number_events.mean() == pytest.approx(expected_month_stats["number_events_mean"]) - assert result.number_access_hours.mean() == pytest.approx(expected_month_stats["number_access_hours_mean"]) - assert result.number_waiting_hours.mean() == pytest.approx(expected_month_stats["number_waiting_hours_mean"]) + assert result.number_events.mean() == pytest.approx( + expected_month_stats["number_events_mean"] + ) + assert result.number_access_hours.mean() == pytest.approx( + expected_month_stats["number_access_hours_mean"] + ) + assert result.number_waiting_hours.mean() == pytest.approx( + expected_month_stats["number_waiting_hours_mean"] + ) From d611912f12edeae29b85fa71f5de09801bcebc95 Mon Sep 17 00:00:00 2001 From: Nicolas Raillard Date: Tue, 30 Jun 2026 10:46:51 +0200 Subject: [PATCH 7/9] Bump version in CITATION.cff --- CITATION.cff | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 2e5fa50..7daa86b 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -8,8 +8,8 @@ type: software license: 'GPL-3.0-or-later' repository-code: "https://resourcecode-project.github.io/py-resourcecode/" url: "https://resourcecode-project.github.io/py-resourcecode/" -version: 1.3.1 -date-released: '2023-03-18' +version: 2.1.0 +date-released: '2026-06-30' authors: - given-names: Nicolas family-names: Raillard @@ -38,4 +38,3 @@ authors: family-names: Papillon email: louis.papillon@innosea.fr affiliation: Innosea - From 5d433b3ca17415ac233e0eea6c81d339a16cff6c Mon Sep 17 00:00:00 2001 From: Nicolas Raillard Date: Tue, 30 Jun 2026 10:47:13 +0200 Subject: [PATCH 8/9] Lint code --- doc/conf.py | 4 +-- resourcecode/client.py | 18 +++------- resourcecode/eva/huseby.py | 23 +++---------- resourcecode/io.py | 8 ++--- resourcecode/opsplanning/__init__.py | 37 +++++--------------- resourcecode/producible_assessment/main.py | 28 ++++----------- resourcecode/resassess/__init__.py | 13 ++----- resourcecode/spectrum/convert2D1D.py | 4 +-- resourcecode/spectrum/download_data.py | 40 +++------------------- resourcecode/spectrum/jonswap.py | 13 ++----- tests/test_client.py | 12 ++----- tests/test_resassess.py | 4 +-- tests/test_spectrum.py | 16 +++------ tests/test_weather_window.py | 12 ++----- 14 files changed, 50 insertions(+), 182 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 4d60c47..b4ea593 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -70,9 +70,7 @@ sphinx_gallery_conf = { "examples_dirs": "../examples", # path to your example scripts "gallery_dirs": "auto_examples", # path to where to save gallery generated output - "within_subsection_order": ( - sphinx_gallery.sorting.FileNameSortKey - ), # to sort gallery examples by file name + "within_subsection_order": (sphinx_gallery.sorting.FileNameSortKey), # to sort gallery examples by file name } # Add any paths that contain templates here, relative to this directory. diff --git a/resourcecode/client.py b/resourcecode/client.py index bb5b81a..b6bca7b 100644 --- a/resourcecode/client.py +++ b/resourcecode/client.py @@ -114,9 +114,7 @@ def get_dataframe( return self.get_dataframe_from_criteria(criteria) - def get_dataframe_from_url( - self, selection_url: str, parameters: Iterable[str] = ("hs",) - ) -> pd.DataFrame: + def get_dataframe_from_url(self, selection_url: str, parameters: Iterable[str] = ("hs",)) -> pd.DataFrame: """Get the pandas dataframe of the data described by the url Parameters @@ -214,14 +212,10 @@ def get_dataframe_from_criteria(self, criteria: Union[str, dict]) -> pd.DataFram try: node_id = int(parsed_criteria["node"]) except ValueError: # failed to convert node to an integer - raise BadPointIdError( - f"Point Id must be an integer, can not be {parsed_criteria['node']!r}" - ) + raise BadPointIdError(f"Point Id must be an integer, can not be {parsed_criteria['node']!r}") else: if node_id not in self.possible_points_id: - raise BadPointIdError( - f"{parsed_criteria['node']} is an unknown pointId." - ) + raise BadPointIdError(f"{parsed_criteria['node']} is an unknown pointId.") # Cassandra database start indexing at 1, so decrement node parsed_criteria["node"] = parsed_criteria["node"] - 1 @@ -304,8 +298,4 @@ def _get_rawdata_from_criteria(self, single_parameter_criteria): if response.ok: return response.json() - raise ValueError( - "Unable to get a response from the database(status code = {})".format( - response.status_code - ) - ) + raise ValueError("Unable to get a response from the database(status code = {})".format(response.status_code)) diff --git a/resourcecode/eva/huseby.py b/resourcecode/eva/huseby.py index c4a130b..f7f5584 100644 --- a/resourcecode/eva/huseby.py +++ b/resourcecode/eva/huseby.py @@ -79,10 +79,7 @@ def huseby(X: np.ndarray, prob: np.ndarray, ntheta: int): Y.sort() C[i, :] = (Y[kc] - Y[kf]) * dk + Y[kf] - ps = ( - ctheta[:, np.newaxis] @ ctheta[:, np.newaxis].T - + stheta[:, np.newaxis] @ stheta[:, np.newaxis].T - ) + ps = ctheta[:, np.newaxis] @ ctheta[:, np.newaxis].T + stheta[:, np.newaxis] @ stheta[:, np.newaxis].T ps[ps < 0] = 0 for iprob in range(len(prob)): @@ -99,9 +96,7 @@ def huseby(X: np.ndarray, prob: np.ndarray, ntheta: int): if M == 3: C = np.ones((ntheta, ntheta, len(prob)), dtype=float) - indj = np.hstack( - (np.arange(0, ntheta / 4 + 1), ntheta - np.arange(1, ntheta / 4 + 1)) - ).astype(int) + indj = np.hstack((np.arange(0, ntheta / 4 + 1), ntheta - np.arange(1, ntheta / 4 + 1))).astype(int) nindj = ntheta / 2 + 1 ncdir = int(ntheta * nindj) cdir = np.zeros((ncdir, M)) @@ -120,12 +115,8 @@ def huseby(X: np.ndarray, prob: np.ndarray, ntheta: int): ps[ps < 0] = 0 a = np.arange(ntheta / 4 + 1, ntheta - ntheta / 4).astype(int) - b = np.hstack((np.arange(ntheta / 2, ntheta), np.arange(0, ntheta / 2))).astype( - int - ) - c = np.hstack( - (np.arange(ntheta / 4 - 1, -1, -1), ntheta - np.arange(1, ntheta / 4)) - ).astype(int) + b = np.hstack((np.arange(ntheta / 2, ntheta), np.arange(0, ntheta / 2))).astype(int) + c = np.hstack((np.arange(ntheta / 4 - 1, -1, -1), ntheta - np.arange(1, ntheta / 4))).astype(int) d = ctheta[:, np.newaxis] @ ctheta[np.newaxis, indj] e = np.repeat(stheta[indj], ntheta).reshape((-1, ntheta)).T f = stheta[:, np.newaxis] @ ctheta[np.newaxis, indj] @@ -159,11 +150,7 @@ def huseby(X: np.ndarray, prob: np.ndarray, ntheta: int): + X_mean[2] ) - Yres[:, iprob] = ( - L[0, 1] * X_std[1] * Xres[:, iprob] - + L[1, 1] * X_std[1] * Yres[:, iprob] - + X_mean[1] - ) + Yres[:, iprob] = L[0, 1] * X_std[1] * Xres[:, iprob] + L[1, 1] * X_std[1] * Yres[:, iprob] + X_mean[1] Xres[:, iprob] = L[0, 0] * X_std[0] * Xres[:, iprob] + X_mean[0] diff --git a/resourcecode/io.py b/resourcecode/io.py index fde697b..df813ad 100644 --- a/resourcecode/io.py +++ b/resourcecode/io.py @@ -38,9 +38,7 @@ NETCFD_DESCRIPTION = json.load(fobj) -def to_netcdf( - dataframe: pd.DataFrame, path: Union[str, Path, None] = None -) -> Union[bytes, "Delayed", None]: +def to_netcdf(dataframe: pd.DataFrame, path: Union[str, Path, None] = None) -> Union[bytes, "Delayed", None]: """Write dataframe contents to a netCFD file. Parameters @@ -84,9 +82,7 @@ def to_mat( """ - df = dataframe.reset_index( - names="time" - ) # convert the pandas index to a proper variable + df = dataframe.reset_index(names="time") # convert the pandas index to a proper variable df.time = 719529 + pd.to_numeric(df.time) / ( 3600 * 1e9 * 24 ) # 1970-01-01 + time in fractional days from nanoseconds diff --git a/resourcecode/opsplanning/__init__.py b/resourcecode/opsplanning/__init__.py index 16351f5..805824e 100644 --- a/resourcecode/opsplanning/__init__.py +++ b/resourcecode/opsplanning/__init__.py @@ -93,9 +93,7 @@ def _get_timestep(data: pd.DataFrame) -> pd.Timedelta: return tstep -def ww_calc( - critsubs: pd.DataFrame, winlen: float, concurrent_windows: bool = True -) -> pd.Series: +def ww_calc(critsubs: pd.DataFrame, winlen: float, concurrent_windows: bool = True) -> pd.Series: """ Method that calculates and returns start date of each weather window @@ -128,9 +126,7 @@ def ww_calc( strtwin = critsubs.index.values[k] thistime = strtwin nexttime = critsubs.index.values[k + 1] - while pd.Timedelta(nexttime - thistime) <= tstep and k < ( - critsubs.shape[0] - 2 - ): + while pd.Timedelta(nexttime - thistime) <= tstep and k < (critsubs.shape[0] - 2): k += 1 if pd.Timedelta(nexttime - strtwin) >= dt.timedelta(seconds=3600 * winlen): windetect.append(strtwin) @@ -203,10 +199,7 @@ def oplen_calc(critsubs, oplen, critical_operation=False, date=1, monstrt=True): elif isinstance(date, int) and date >= 1: dayval = date else: - msg = ( - "Variable date in monthly results calculation should be" - " positive integer or datetime object" - ) + msg = "Variable date in monthly results calculation should be" " positive integer or datetime object" raise NameError(msg) yerng = [min(critsubs.index).year, max(critsubs.index).year] morng = [min(critsubs.index).month, max(critsubs.index).month] @@ -217,19 +210,13 @@ def oplen_calc(critsubs, oplen, critical_operation=False, date=1, monstrt=True): tz="utc", ) daterng = daterng.shift(dayval - 1, freq="D") - oplendetect = pd.Series( - np.zeros(daterng.shape[0], dtype="timedelta64[s]"), index=daterng - ) + oplendetect = pd.Series(np.zeros(daterng.shape[0], dtype="timedelta64[s]"), index=daterng) elif monstrt is False: if isinstance(date, dt.datetime): daterng = pd.date_range(start=date, end=date) - oplendetect = pd.Series( - np.zeros(daterng.shape[0], dtype="timedelta64[s]"), index=daterng - ) + oplendetect = pd.Series(np.zeros(daterng.shape[0], dtype="timedelta64[s]"), index=daterng) else: - msg = ( - "Variable date in single result calculation should be a datetime object" - ) + msg = "Variable date in single result calculation should be a datetime object" raise NameError(msg) else: raise NameError("Input option monstrt should be boolean") @@ -285,9 +272,7 @@ def wwmonstats(windetect): yeun = windetect.dt.year.unique() moun = windetect.dt.month.unique() yeun.sort() - wwmonres = pd.DataFrame( - np.zeros([yeun.shape[0], moun.shape[0]]), columns=moun, index=yeun - ) + wwmonres = pd.DataFrame(np.zeros([yeun.shape[0], moun.shape[0]]), columns=moun, index=yeun) for ye, mo in product(yeun, moun): subs = windetect[(windetect.dt.year == ye) & (windetect.dt.month == mo)] @@ -319,12 +304,8 @@ def olmonstats(oplendetect): moun.sort() yeun.sort() - olmonres = pd.DataFrame( - np.zeros([yeun.shape[0], moun.shape[0]]), columns=moun, index=yeun - ) + olmonres = pd.DataFrame(np.zeros([yeun.shape[0], moun.shape[0]]), columns=moun, index=yeun) for ye, mo in product(yeun, moun): - subs = oplendetect[ - (oplendetect.index.year == ye) & (oplendetect.index.month == mo) - ] + subs = oplendetect[(oplendetect.index.year == ye) & (oplendetect.index.month == mo)] olmonres.at[ye, mo] = subs[0].days * 24 + subs[0].seconds / 3600 return olmonres diff --git a/resourcecode/producible_assessment/main.py b/resourcecode/producible_assessment/main.py index 7b97cc8..69aabfc 100644 --- a/resourcecode/producible_assessment/main.py +++ b/resourcecode/producible_assessment/main.py @@ -40,25 +40,19 @@ def __init__(self, capture_width, s): self.times = s.index # time vector self.freqs = s.columns # frequency vector # time domain data - self.power = pd.DataFrame( - np.zeros(len(self.times)), index=self.times - ) # absorbed power (W) + self.power = pd.DataFrame(np.zeros(len(self.times)), index=self.times) # absorbed power (W) # absorbed power, no reduction (W) self.power_no_red = pd.DataFrame(np.zeros(len(self.times)), index=self.times) self.mean_power = None # mean absorbed power (W) self.mean_power_no_red = None # mean absorbed power, no reduction (W) self.median_power = None # median absorbed power (W) self.median_power_no_red = None # median absorbed power, no reduction (W) - self.pto_damp = pd.DataFrame( - np.zeros(len(self.times)), index=self.times - ) # PTO damping (Ns/m) + self.pto_damp = pd.DataFrame(np.zeros(len(self.times)), index=self.times) # PTO damping (Ns/m) # PTO damping, no reduction (Ns/m) self.pto_damp_no_red = pd.DataFrame(np.zeros(len(self.times)), index=self.times) self.cumulative_power = None # cumulative power (W) # frequency domain data - self.freq_data = ( - None # contains Hs, Tp, absorbed power and PTO damping in frequency domain - ) + self.freq_data = None # contains Hs, Tp, absorbed power and PTO damping in frequency domain # interpolate frequencies (if needed) to match sea-state and PTO capture width data self.interp_freq() # power computation @@ -95,9 +89,7 @@ def interp_freq(self): continue known_freqs.add(freq) - self.capture_width = self.capture_width.append( - pd.Series(name=freq, dtype="float64") - ) + self.capture_width = self.capture_width.append(pd.Series(name=freq, dtype="float64")) self.capture_width.sort_index(inplace=True) self.capture_width = self.capture_width.interpolate() @@ -176,16 +168,12 @@ def get_power_pto_damp(self): "PTO damping": self.pto_damp.values.flatten(), } ) - self.mean_power = pd.DataFrame( - data=self.power.mean()[0] * np.ones(len(self.times)), index=self.times - ) + self.mean_power = pd.DataFrame(data=self.power.mean()[0] * np.ones(len(self.times)), index=self.times) self.mean_power_no_red = pd.DataFrame( data=self.power_no_red.mean()[0] * np.ones(len(self.times)), index=self.times, ) - self.median_power = pd.DataFrame( - data=self.power.median()[0] * np.ones(len(self.times)), index=self.times - ) + self.median_power = pd.DataFrame(data=self.power.median()[0] * np.ones(len(self.times)), index=self.times) self.median_power_no_red = pd.DataFrame( data=self.power_no_red.median()[0] * np.ones(len(self.times)), index=self.times, @@ -195,9 +183,7 @@ def get_cumulative_power(self): """Compute PTO cumulative power""" power_ordered = self.power.sort_values(by=0) - self.cumulative_power = pd.DataFrame( - data=100 * power_ordered.cumsum() / power_ordered.sum() - ) + self.cumulative_power = pd.DataFrame(data=100 * power_ordered.cumsum() / power_ordered.sum()) @staticmethod def compute_spectrum_moment(f, s, n=0): diff --git a/resourcecode/resassess/__init__.py b/resourcecode/resassess/__init__.py index cb672fb..9aad1b1 100644 --- a/resourcecode/resassess/__init__.py +++ b/resourcecode/resassess/__init__.py @@ -71,9 +71,7 @@ def exceed(df: pd.DataFrame) -> Tuple[pd.DataFrame, np.ndarray]: return datasrt, exceedance -def univar_monstats( - df: pd.DataFrame, varnm: str -) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]: +def univar_monstats(df: pd.DataFrame, varnm: str) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]: """ Method to calculate univariate statistics for any input variable. Used in resource assessment to produce deliverables as per IEC @@ -101,10 +99,7 @@ def univar_monstats( """ if varnm not in df.columns: existing_col = ", ".join(sorted(df.columns)) - raise NameError( - f"Parameter {varnm} is not in the dataframe. " - f"Possible values are: {existing_col}" - ) + raise NameError(f"Parameter {varnm} is not in the dataframe. " f"Possible values are: {existing_col}") # sort rows according to the variable selected sorted_df = df[[varnm]].sort_values(by=varnm) sorted_df.index = pd.to_datetime(df.index, format="%d/%m/%Y %H:%M") @@ -113,9 +108,7 @@ def univar_monstats( sorted_df["month_index"] = sorted_df.index.month sorted_df["month"] = sorted_df.index.month_name() sorted_df["year"] = sorted_df.index.year - sorted_df["Exceedance"] = sorted_df.groupby("month")[varnm].rank( - method="min", pct=True - ) + sorted_df["Exceedance"] = sorted_df.groupby("month")[varnm].rank(method="min", pct=True) res = [sorted_df] diff --git a/resourcecode/spectrum/convert2D1D.py b/resourcecode/spectrum/convert2D1D.py index 45b3c7d..b577050 100644 --- a/resourcecode/spectrum/convert2D1D.py +++ b/resourcecode/spectrum/convert2D1D.py @@ -22,9 +22,7 @@ import xarray -def raw_convert_spectrum_2Dto1D( - spectrum_2D: np.ndarray, vdir: np.ndarray -) -> np.ndarray: +def raw_convert_spectrum_2Dto1D(spectrum_2D: np.ndarray, vdir: np.ndarray) -> np.ndarray: """ Converts the 2D spectrum to a 1D spectrum diff --git a/resourcecode/spectrum/download_data.py b/resourcecode/spectrum/download_data.py index e405efb..09dcccc 100644 --- a/resourcecode/spectrum/download_data.py +++ b/resourcecode/spectrum/download_data.py @@ -54,26 +54,12 @@ def download_single_2D_file( A dataset object with the data read from the downloaded netCDF file. """ base = "ftp://ftp.ifremer.fr/ifremer/dataref/ww3/resourcecode/HINDCAST/" - url = ( - base - + year - + "/" - + month - + "/SPEC_NC/RSCD_WW3-RSCD-UG-" - + point - + "_" - + year - + month - + "_spec.nc" - ) + url = base + year + "/" + month + "/SPEC_NC/RSCD_WW3-RSCD-UG-" + point + "_" + year + month + "_spec.nc" if point not in set(get_grid_spec().name): raise ValueError(f"{point} is an unkown location") - if ( - int(year) < get_covered_period()["start"].year - or int(year) > get_covered_period()["end"].year - ): + if int(year) < get_covered_period()["start"].year or int(year) > get_covered_period()["end"].year: raise ValueError(f"{year} is outsite the covered period") if int(month) < 1 or int(month) > 12: @@ -125,32 +111,16 @@ def download_single_1D_file( A dataset object with the data read from the downloaded netCDF file. """ base = "ftp://ftp.ifremer.fr/ifremer/dataref/ww3/resourcecode/HINDCAST/" - url = ( - base - + year - + "/" - + month - + "/FREQ_NC/RSCD_WW3-RSCD-UG-" - + point - + "_" - + year - + month - + "_freq.nc" - ) + url = base + year + "/" + month + "/FREQ_NC/RSCD_WW3-RSCD-UG-" + point + "_" + year + month + "_freq.nc" if point not in set(get_grid_spec().name): raise ValueError(f"{point} is an unkown location") - if ( - int(year) < get_covered_period()["start"].year - or int(year) > get_covered_period()["end"].year - ): + if int(year) < get_covered_period()["start"].year or int(year) > get_covered_period()["end"].year: raise ValueError(f"{year} is outsite the covered period") if int(month) < 1 or int(month) > 12: - raise ValueError( - f"{month} must by between 1 and 12 with a leading zero if needed." - ) + raise ValueError(f"{month} must by between 1 and 12 with a leading zero if needed.") with contextlib.closing(urllib.request.urlopen(url)) as response: with tempfile.NamedTemporaryFile(delete=False, suffix=".nc") as tmp_file: diff --git a/resourcecode/spectrum/jonswap.py b/resourcecode/spectrum/jonswap.py index dfb1306..7e73a01 100644 --- a/resourcecode/spectrum/jonswap.py +++ b/resourcecode/spectrum/jonswap.py @@ -50,22 +50,13 @@ def jonswap(hs: float, tp: float, gamma: float, freq: np.ndarray) -> np.ndarray: * (hs**2) / (tp**4) * np.exp(-5.0 / (4 * tp**4) / (freq**4)) - * gamma - ** ( - np.exp( - -((freq - 1 / tp) ** 2) - * (tp**2) - / (2 * (np.where(freq < (1.0 / tp), 0.07, 0.09) ** 2)) - ) - ) + * gamma ** (np.exp(-((freq - 1 / tp) ** 2) * (tp**2) / (2 * (np.where(freq < (1.0 / tp), 0.07, 0.09) ** 2)))) ) alpha = (hs**2) / (16 * np.trapezoid(sf, x=freq)) return alpha * sf -def compute_jonswap_wave_spectrum( - seastate_data: pd.DataFrame, freq: np.ndarray, gamma: float = 1 -) -> pd.DataFrame: +def compute_jonswap_wave_spectrum(seastate_data: pd.DataFrame, freq: np.ndarray, gamma: float = 1) -> pd.DataFrame: """Computes JONSWAP wave spectrum time series from Hs and Tp time series Parameters diff --git a/tests/test_client.py b/tests/test_client.py index 95b0bb5..9316719 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -60,9 +60,7 @@ def mock_requests_get_raw_data(query_url, parameters): for date, value in data["result"]["data"]: # start_date and end_date must be multiplied by 1e3, because # cassandra returns milliseconds - if date is not None and ( - start_date is not None and date < start_date * 1e3 - ): + if date is not None and (start_date is not None and date < start_date * 1e3): continue if date and end_date is not None and date > end_date * 1e3: @@ -139,9 +137,7 @@ def test_get_raw_data(): parameter = "fp" client = resourcecode.Client() - with mock.patch( - "requests.get", side_effect=mock_requests_get_raw_data - ) as mock_requests_get: + with mock.patch("requests.get", side_effect=mock_requests_get_raw_data) as mock_requests_get: json_data = client._get_rawdata_from_criteria( { "parameter": [ @@ -150,9 +146,7 @@ def test_get_raw_data(): } ) - mock_requests_get.assert_called_once_with( - client.cassandra_base_url + "api/timeseries", {"parameter": [parameter]} - ) + mock_requests_get.assert_called_once_with(client.cassandra_base_url + "api/timeseries", {"parameter": [parameter]}) assert json_data["query"]["parameterCode"] == parameter dataset_size = json_data["result"]["dataSetSize"] diff --git a/tests/test_resassess.py b/tests/test_resassess.py index 322ed2b..31ae828 100644 --- a/tests/test_resassess.py +++ b/tests/test_resassess.py @@ -85,9 +85,7 @@ def test_univar_monstats(data): stored_results = ("monthly_stat.csv", "yearly_stat.csv") for result, expected_result_path in zip((dtm, dty), stored_results): path = DATA_DIR / "resassess" / expected_result_path - expected_result = pd.read_csv( - path, index_col=0, dtype={"month_index": np.int32} - ) + expected_result = pd.read_csv(path, index_col=0, dtype={"month_index": np.int32}) # count is the first column return by describe count_index = result.columns.get_loc("count") diff --git a/tests/test_spectrum.py b/tests/test_spectrum.py index f6383fd..72e7f68 100644 --- a/tests/test_spectrum.py +++ b/tests/test_spectrum.py @@ -51,9 +51,7 @@ def test_compute_parameter_1D(): depth = float(np.loadtxt(DATA_DIR / "spectrum" / "depth.csv", delimiter=",")) etfh = np.loadtxt(DATA_DIR / "spectrum" / "Etfh.csv", delimiter=",") - expected_parameters = SeaStatesParameters( - *np.loadtxt(DATA_DIR / "spectrum" / "parameters_1D.csv", delimiter=",") - ) + expected_parameters = SeaStatesParameters(*np.loadtxt(DATA_DIR / "spectrum" / "parameters_1D.csv", delimiter=",")) got_parameters = raw_compute_parameters_from_1D_spectrum(etfh, freq, depth) assert got_parameters.approx(expected_parameters) @@ -65,9 +63,7 @@ def test_compute_parameter_2D(): vdir = np.loadtxt(DATA_DIR / "spectrum" / "dir.csv", delimiter=",") depth = float(np.loadtxt(DATA_DIR / "spectrum" / "depth.csv", delimiter=",")) - expected_parameters = SeaStatesParameters( - *np.loadtxt(DATA_DIR / "spectrum" / "parameters_2D.csv", delimiter=",") - ) + expected_parameters = SeaStatesParameters(*np.loadtxt(DATA_DIR / "spectrum" / "parameters_2D.csv", delimiter=",")) got_parameters = raw_compute_parameters_from_2D_spectrum(spec, freq, vdir, depth) assert got_parameters.approx(expected_parameters) @@ -88,9 +84,7 @@ def test_download_2D_file(): scale_factor = expected_spectrum["efth"].encoding.get("scale_factor", 1.0) add_offset = expected_spectrum["efth"].encoding.get("add_offset", 0.0) efth_decoded = efth_encoded * scale_factor + add_offset - expected_spectrum = expected_spectrum.assign( - Ef=np.power(10.0, efth_decoded) - 1e-12 - ) + expected_spectrum = expected_spectrum.assign(Ef=np.power(10.0, efth_decoded) - 1e-12) expected_spectrum = expected_spectrum.drop_vars("efth") got_spectrum = get_2D_spectrum("W001933N55743", ["2016"], ["05"]) @@ -99,9 +93,7 @@ def test_download_2D_file(): def test_download_1D_file(): - expected_spectrum = xarray.open_dataset( - DATA_DIR / "spectrum" / "RSCD_WW3-RSCD-UG-W001933N55743_201605_freq.nc" - ) + expected_spectrum = xarray.open_dataset(DATA_DIR / "spectrum" / "RSCD_WW3-RSCD-UG-W001933N55743_201605_freq.nc") expected_spectrum = expected_spectrum.drop_dims("string40").squeeze() expected_spectrum = expected_spectrum.drop_vars(["station"]) diff --git a/tests/test_weather_window.py b/tests/test_weather_window.py index 6f4bebd..f228e29 100644 --- a/tests/test_weather_window.py +++ b/tests/test_weather_window.py @@ -82,12 +82,6 @@ def test_weather_windows(hs): result = compute_weather_windows(hs, month) assert result.PT.mean() == pytest.approx(expected_month_stats["PT_mean"]) - assert result.number_events.mean() == pytest.approx( - expected_month_stats["number_events_mean"] - ) - assert result.number_access_hours.mean() == pytest.approx( - expected_month_stats["number_access_hours_mean"] - ) - assert result.number_waiting_hours.mean() == pytest.approx( - expected_month_stats["number_waiting_hours_mean"] - ) + assert result.number_events.mean() == pytest.approx(expected_month_stats["number_events_mean"]) + assert result.number_access_hours.mean() == pytest.approx(expected_month_stats["number_access_hours_mean"]) + assert result.number_waiting_hours.mean() == pytest.approx(expected_month_stats["number_waiting_hours_mean"]) From c45703da638c0b96fece7dd94f85a0b8bededefd Mon Sep 17 00:00:00 2001 From: Nicolas Raillard Date: Tue, 30 Jun 2026 10:52:51 +0200 Subject: [PATCH 9/9] Update publish-to-pypi.yml --- .github/workflows/publish-to-pypi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 04dc561..89d4713 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -48,7 +48,7 @@ jobs: - name: Verify version consistency run: | # Extraire la version du package installé - PACKAGE_VERSION=$(python -c "import resourcecode; print(resourcecode.__version__.__version__)") + PACKAGE_VERSION=$(python -c "import resourcecode; print(resourcecode.__version__)") TAG_VERSION=${GITHUB_REF#refs/tags/v} echo "Tag version: $TAG_VERSION"