From a034c968ab657750a8b0cab590958833e3e1e4ac Mon Sep 17 00:00:00 2001 From: Henrik Andersson Date: Thu, 5 Oct 2023 18:46:48 +0200 Subject: [PATCH 01/10] sel with physical coords (Grid2d) --- mikeio/dataset/_dataarray.py | 28 ++++++++++++++++++++++++++++ tests/test_dataarray.py | 12 ++++++++++++ 2 files changed, 40 insertions(+) diff --git a/mikeio/dataset/_dataarray.py b/mikeio/dataset/_dataarray.py index b3c35a047..9c70827f7 100644 --- a/mikeio/dataset/_dataarray.py +++ b/mikeio/dataset/_dataarray.py @@ -841,8 +841,36 @@ def sel( """ da = self + + slice_sel = False + # select in space if len(kwargs) > 0: + + # loop through kwargs and if any is a slice, find the index of start and stop and create a new slice with logical index + for k, v in kwargs.items(): + if isinstance(v, slice): + slice_sel = True + idx_start = self.geometry.find_index(**{k:v.start}) + idx_stop = self.geometry.find_index(**{k:v.stop}) + pos = 0 + if isinstance(idx_start, tuple): + if k == "x": + pos = 0 + if k == "y": + pos = 1 + + start = idx_start[pos][0] if idx_start is not None else None + stop = idx_stop[pos][0] if idx_stop is not None else None + + idx = slice(start, stop) + + da = da.isel(idx, axis=k) + + if slice_sel: + return da + + idx = self.geometry.find_index(**kwargs) if isinstance(idx, tuple): # TODO: support for dfs3 diff --git a/tests/test_dataarray.py b/tests/test_dataarray.py index 8f8803560..67e0f0833 100644 --- a/tests/test_dataarray.py +++ b/tests/test_dataarray.py @@ -851,6 +851,18 @@ def test_modify_values_1d(da1): da1.isel([0, 4, 7]).values[1] = 10.0 assert da1.values[4] != 10.0 +def test_get_2d_slice_with_sel(da_grid2d): + assert da_grid2d.shape == (10, 14, 7) + da3 = da_grid2d.sel(x=slice(10.0, 10.3)) + assert da3.shape == (10, 14,3) + da4 = da_grid2d.sel(y=slice(-5.0, 0.0)) + assert da4.shape == (10, 5, 7) + + da5 = da_grid2d.sel(x=slice(10.0, 10.3), y=slice(-5.0,0.0)) + assert da5.shape == (10,5,3) + + da6 = da_grid2d.sel(x=slice(None, 10.3), y=slice(-4.0, None)) + assert da6.shape == (10, 8, 3) def test_modify_values_2d_all(da2): assert da2.shape == (10, 7) From 116009f777b8cc68abfbde5ffc6add633879a11b Mon Sep 17 00:00:00 2001 From: Henrik Andersson Date: Thu, 5 Oct 2023 18:47:01 +0200 Subject: [PATCH 02/10] Bad type hints --- mikeio/spatial/_FM_geometry.py | 4 ++-- mikeio/spatial/_FM_utils.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/mikeio/spatial/_FM_geometry.py b/mikeio/spatial/_FM_geometry.py index daca0f11b..38618f824 100644 --- a/mikeio/spatial/_FM_geometry.py +++ b/mikeio/spatial/_FM_geometry.py @@ -830,7 +830,7 @@ def boundary_polylines(self) -> BoundaryPolylines: """Lists of closed polylines defining domain outline""" return self._get_boundary_polylines() - def contains(self, points) -> Sequence[bool]: + def contains(self, points): """test if a list of points are contained by mesh Parameters @@ -1037,7 +1037,7 @@ def find_index(self, x=None, y=None, coords=None, area=None) -> np.ndarray: raise ValueError("Provide either coordinates or area") @staticmethod - def _inside_polygon(polygon, xy) -> bool: + def _inside_polygon(polygon, xy): import matplotlib.path as mp if polygon.ndim == 1: diff --git a/mikeio/spatial/_FM_utils.py b/mikeio/spatial/_FM_utils.py index 65603bd27..823943669 100644 --- a/mikeio/spatial/_FM_utils.py +++ b/mikeio/spatial/_FM_utils.py @@ -98,6 +98,7 @@ def _plot_map( import matplotlib.pyplot as plt import matplotlib.cm as cm + import matplotlib VALID_PLOT_TYPES = ( "mesh_only", @@ -111,7 +112,7 @@ def _plot_map( ok_list = ", ".join(VALID_PLOT_TYPES) raise Exception(f"plot_type {plot_type} unknown! ({ok_list})") - cmap = cmap or cm.viridis + cmap = cmap or matplotlib.colormaps["viridis"] nc = node_coordinates ec = element_coordinates From 7d34bc33a9074b523c2b3078a953eb14ec047117 Mon Sep 17 00:00:00 2001 From: Henrik Andersson Date: Fri, 6 Oct 2023 08:21:52 +0200 Subject: [PATCH 03/10] Extract method --- mikeio/dataset/_dataarray.py | 53 +++++++++++++++++------------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/mikeio/dataset/_dataarray.py b/mikeio/dataset/_dataarray.py index 9c70827f7..5433b3d22 100644 --- a/mikeio/dataset/_dataarray.py +++ b/mikeio/dataset/_dataarray.py @@ -3,7 +3,7 @@ from copy import deepcopy from datetime import datetime from functools import cached_property -from typing import Iterable, Optional, Sequence, Tuple +from typing import Iterable, Optional, Sequence, Tuple, Mapping import numpy as np @@ -839,38 +839,14 @@ def sel( time: 1997-09-15 21:00:00 - 1997-09-16 03:00:00 (3 records) geometry: Dfsu2D (3700 elements, 2090 nodes) """ + if any([isinstance(v, slice) for v in kwargs.values()]): + return self._sel_with_slice(kwargs) + da = self - - slice_sel = False - # select in space if len(kwargs) > 0: - # loop through kwargs and if any is a slice, find the index of start and stop and create a new slice with logical index - for k, v in kwargs.items(): - if isinstance(v, slice): - slice_sel = True - idx_start = self.geometry.find_index(**{k:v.start}) - idx_stop = self.geometry.find_index(**{k:v.stop}) - pos = 0 - if isinstance(idx_start, tuple): - if k == "x": - pos = 0 - if k == "y": - pos = 1 - - start = idx_start[pos][0] if idx_start is not None else None - stop = idx_stop[pos][0] if idx_stop is not None else None - - idx = slice(start, stop) - - da = da.isel(idx, axis=k) - - if slice_sel: - return da - - idx = self.geometry.find_index(**kwargs) if isinstance(idx, tuple): # TODO: support for dfs3 @@ -894,6 +870,27 @@ def sel( da = da[time] # __getitem__ is 🚀 return da + + def _sel_with_slice(self, kwargs: Mapping[str,slice]) -> "DataArray": + for k, v in kwargs.items(): + if isinstance(v, slice): + idx_start = self.geometry.find_index(**{k:v.start}) + idx_stop = self.geometry.find_index(**{k:v.stop}) + pos = 0 + if isinstance(idx_start, tuple): + if k == "x": + pos = 0 + if k == "y": + pos = 1 + + start = idx_start[pos][0] if idx_start is not None else None + stop = idx_stop[pos][0] if idx_stop is not None else None + + idx = slice(start, stop) + + self = self.isel(idx, axis=k) + + return self def interp( # TODO find out optimal syntax to allow interpolation to single point, new time, grid, mesh... From e1253b7beec71f0589f10f5d2a501ca478b22c73 Mon Sep 17 00:00:00 2001 From: Henrik Andersson Date: Fri, 6 Oct 2023 08:22:06 +0200 Subject: [PATCH 04/10] Make it --- Makefile | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index b7823e3b6..f17987469 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,23 @@ -build: test - #python setup.py sdist bdist_wheel +LIB = mikeio + +check: lint typecheck test + +build: typecheck test python -m build +lint: + ruff . + test: pytest --disable-warnings +typecheck: + mypy $(LIB)/ --config-file pyproject.toml + +coverage: + pytest --cov-report html --cov=$(LIB) tests/ + doctest: pytest mikeio/dfs/*.py mikeio/dfsu/*.py mikeio/eum/*.py mikeio/pfs/*.py mikeio/spatial/_grid_geometry.py --doctest-modules rm -f *.dfs* # remove temporary files, created from doctests @@ -13,13 +25,9 @@ doctest: perftest: pytest tests/performance/ --durations=0 -typecheck: - mypy mikeio/ - -coverage: - pytest --cov-report html --cov=mikeio tests/ - docs: FORCE cd docs; make html ;cd - FORCE: + + From 2898139be69bfe19876ade5ce9fe5545c933c10b Mon Sep 17 00:00:00 2001 From: Henrik Andersson Date: Fri, 6 Oct 2023 08:22:15 +0200 Subject: [PATCH 05/10] More type hints --- mikeio/spatial/_FM_geometry.py | 2 +- mikeio/spatial/_FM_utils.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/mikeio/spatial/_FM_geometry.py b/mikeio/spatial/_FM_geometry.py index 38618f824..ba5b0eafb 100644 --- a/mikeio/spatial/_FM_geometry.py +++ b/mikeio/spatial/_FM_geometry.py @@ -2,7 +2,7 @@ import warnings from collections import namedtuple from functools import cached_property -from typing import Collection, Sequence, Optional, List +from typing import Collection, Optional, List import numpy as np from mikecore.DfsuFile import DfsuFileType # type: ignore diff --git a/mikeio/spatial/_FM_utils.py b/mikeio/spatial/_FM_utils.py index 823943669..498ba716d 100644 --- a/mikeio/spatial/_FM_utils.py +++ b/mikeio/spatial/_FM_utils.py @@ -97,7 +97,6 @@ def _plot_map( """ import matplotlib.pyplot as plt - import matplotlib.cm as cm import matplotlib VALID_PLOT_TYPES = ( From 507c0e9bae2a355fcde33f2ccc7e4ab5b04410c1 Mon Sep 17 00:00:00 2001 From: Henrik Andersson Date: Mon, 9 Oct 2023 08:58:18 +0200 Subject: [PATCH 06/10] Raise OutsideModelDomainError --- mikeio/spatial/_grid_geometry.py | 4 ++++ tests/test_consistency.py | 16 ++++++++-------- tests/test_dataarray.py | 7 +++++++ tests/test_dfsu.py | 4 ++-- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/mikeio/spatial/_grid_geometry.py b/mikeio/spatial/_grid_geometry.py index d18a13248..7bb20bd21 100644 --- a/mikeio/spatial/_grid_geometry.py +++ b/mikeio/spatial/_grid_geometry.py @@ -744,8 +744,12 @@ def find_index( raise ValueError("x,y and coords cannot be given at the same time!") coords = np.column_stack([np.atleast_1d(x), np.atleast_1d(y)]) elif x is not None: + if x < self.x[0] or x > self.x[-1]: + raise OutsideModelDomainError(x=x, y=None) return np.atleast_1d(np.argmin(np.abs(self.x - x))), None elif y is not None: + if y < self.y[0] or y > self.y[-1]: + raise OutsideModelDomainError(x=None, y=y) return None, np.atleast_1d(np.argmin(np.abs(self.y - y))) if coords is not None: diff --git a/tests/test_consistency.py b/tests/test_consistency.py index 528b1a332..0b94d3a6d 100644 --- a/tests/test_consistency.py +++ b/tests/test_consistency.py @@ -95,12 +95,12 @@ def test_dfs1_interp_x(): # ds = mikeio.read("tests/testdata/consistency/oresundHD.dfsu") # g = ds.geometry.get_overset_grid(dx=5000) # dsi = ds.interp_like(g) -# dsi.to_dfs("tests/testdata/consistency/oresundHD.dfs2") +# dsi.to_dfs("tests/testdata/consistency/oresundHD2.dfs2") def test_read_dfs2(): ds = mikeio.read( - "tests/testdata/consistency/oresundHD.dfs2", + "tests/testdata/consistency/oresundHD2.dfs2", items=[0, 1], time=slice("2018", "2018-03-10"), ) @@ -112,7 +112,7 @@ def test_read_dfs2(): def test_sel_line_dfs2(): x = 350000 y = 6145000 - ds = mikeio.read("tests/testdata/consistency/oresundHD.dfs2") + ds = mikeio.read("tests/testdata/consistency/oresundHD2.dfs2") dsselx = ds.sel(x=x) assert isinstance(dsselx.geometry, mikeio.Grid1D) dssely = ds.sel(y=y) @@ -122,7 +122,7 @@ def test_sel_line_dfs2(): def test_sel_mult_line_not_possible(): xs = [350000, 360000] - ds = mikeio.read("tests/testdata/consistency/oresundHD.dfs2") + ds = mikeio.read("tests/testdata/consistency/oresundHD2.dfs2") with pytest.raises( Exception, match="scalar" ): # NotImplemented or ValueError not sure yet @@ -131,7 +131,7 @@ def test_sel_mult_line_not_possible(): def test_read_dfs2_single_time(): ds = mikeio.read( - "tests/testdata/consistency/oresundHD.dfs2", + "tests/testdata/consistency/oresundHD2.dfs2", time=-1, ) @@ -139,7 +139,7 @@ def test_read_dfs2_single_time(): assert "time" not in ds.dims ds = mikeio.read( - "tests/testdata/consistency/oresundHD.dfs2", + "tests/testdata/consistency/oresundHD2.dfs2", time="2018-03-10", ) @@ -147,7 +147,7 @@ def test_read_dfs2_single_time(): assert "time" not in ds.dims ds = mikeio.read( - "tests/testdata/consistency/oresundHD.dfs2", time=[-1], keepdims=True + "tests/testdata/consistency/oresundHD2.dfs2", time=[-1], keepdims=True ) assert ds.n_timesteps == 1 @@ -166,7 +166,7 @@ def test_read_single_row_dfs2_single_time_step(): def test_interp_x_y_dfs2(): - ds = mikeio.read("tests/testdata/consistency/oresundHD.dfs2") + ds = mikeio.read("tests/testdata/consistency/oresundHD2.dfs2") x = 350000 y = 6145000 diff --git a/tests/test_dataarray.py b/tests/test_dataarray.py index 67e0f0833..1f571bc4f 100644 --- a/tests/test_dataarray.py +++ b/tests/test_dataarray.py @@ -864,6 +864,13 @@ def test_get_2d_slice_with_sel(da_grid2d): da6 = da_grid2d.sel(x=slice(None, 10.3), y=slice(-4.0, None)) assert da6.shape == (10, 8, 3) +def test_get_2d_outside_domain_raises_error(da_grid2d): + with pytest.raises(OutsideModelDomainError): + da_grid2d.sel(x=0.0) + + with pytest.raises(OutsideModelDomainError): + da_grid2d.sel(x=slice(0.0,1.0)) + def test_modify_values_2d_all(da2): assert da2.shape == (10, 7) assert da2.values[2, 5] == 0.1 diff --git a/tests/test_dfsu.py b/tests/test_dfsu.py index d4e8b5a02..4d01dc0ec 100644 --- a/tests/test_dfsu.py +++ b/tests/test_dfsu.py @@ -1076,7 +1076,7 @@ def test_interp_like_dataarray(tmp_path): tmp_path / "interp.dfs2" da = mikeio.read("tests/testdata/consistency/oresundHD.dfsu")[0] - da2 = mikeio.read("tests/testdata/consistency/oresundHD.dfs2", time=[0, 1])[0] + da2 = mikeio.read("tests/testdata/consistency/oresundHD2.dfs2", time=[0, 1])[0] dai = da.interp_like(da2) assert isinstance(dai, DataArray) @@ -1096,7 +1096,7 @@ def test_interp_like_dataset(tmp_path): fp = tmp_path / "interp.dfs2" ds = mikeio.read("tests/testdata/consistency/oresundHD.dfsu") - ds2 = mikeio.read("tests/testdata/consistency/oresundHD.dfs2", time=[0, 1]) + ds2 = mikeio.read("tests/testdata/consistency/oresundHD2.dfs2", time=[0, 1]) dsi = ds.interp_like(ds2) assert isinstance(dsi, Dataset) From e0232dbad56efc52c46605c49364053ca53adcf2 Mon Sep 17 00:00:00 2001 From: Henrik Andersson Date: Mon, 9 Oct 2023 09:18:02 +0200 Subject: [PATCH 07/10] Delete bad file --- tests/test_consistency.py | 16 ++++++++-------- tests/test_dfsu.py | 4 ++-- tests/testdata/consistency/oresundHD.dfs2 | Bin 15631 -> 19471 bytes tests/testdata/consistency/oresundHD2.dfs2 | Bin 19471 -> 0 bytes 4 files changed, 10 insertions(+), 10 deletions(-) delete mode 100644 tests/testdata/consistency/oresundHD2.dfs2 diff --git a/tests/test_consistency.py b/tests/test_consistency.py index 0b94d3a6d..528b1a332 100644 --- a/tests/test_consistency.py +++ b/tests/test_consistency.py @@ -95,12 +95,12 @@ def test_dfs1_interp_x(): # ds = mikeio.read("tests/testdata/consistency/oresundHD.dfsu") # g = ds.geometry.get_overset_grid(dx=5000) # dsi = ds.interp_like(g) -# dsi.to_dfs("tests/testdata/consistency/oresundHD2.dfs2") +# dsi.to_dfs("tests/testdata/consistency/oresundHD.dfs2") def test_read_dfs2(): ds = mikeio.read( - "tests/testdata/consistency/oresundHD2.dfs2", + "tests/testdata/consistency/oresundHD.dfs2", items=[0, 1], time=slice("2018", "2018-03-10"), ) @@ -112,7 +112,7 @@ def test_read_dfs2(): def test_sel_line_dfs2(): x = 350000 y = 6145000 - ds = mikeio.read("tests/testdata/consistency/oresundHD2.dfs2") + ds = mikeio.read("tests/testdata/consistency/oresundHD.dfs2") dsselx = ds.sel(x=x) assert isinstance(dsselx.geometry, mikeio.Grid1D) dssely = ds.sel(y=y) @@ -122,7 +122,7 @@ def test_sel_line_dfs2(): def test_sel_mult_line_not_possible(): xs = [350000, 360000] - ds = mikeio.read("tests/testdata/consistency/oresundHD2.dfs2") + ds = mikeio.read("tests/testdata/consistency/oresundHD.dfs2") with pytest.raises( Exception, match="scalar" ): # NotImplemented or ValueError not sure yet @@ -131,7 +131,7 @@ def test_sel_mult_line_not_possible(): def test_read_dfs2_single_time(): ds = mikeio.read( - "tests/testdata/consistency/oresundHD2.dfs2", + "tests/testdata/consistency/oresundHD.dfs2", time=-1, ) @@ -139,7 +139,7 @@ def test_read_dfs2_single_time(): assert "time" not in ds.dims ds = mikeio.read( - "tests/testdata/consistency/oresundHD2.dfs2", + "tests/testdata/consistency/oresundHD.dfs2", time="2018-03-10", ) @@ -147,7 +147,7 @@ def test_read_dfs2_single_time(): assert "time" not in ds.dims ds = mikeio.read( - "tests/testdata/consistency/oresundHD2.dfs2", time=[-1], keepdims=True + "tests/testdata/consistency/oresundHD.dfs2", time=[-1], keepdims=True ) assert ds.n_timesteps == 1 @@ -166,7 +166,7 @@ def test_read_single_row_dfs2_single_time_step(): def test_interp_x_y_dfs2(): - ds = mikeio.read("tests/testdata/consistency/oresundHD2.dfs2") + ds = mikeio.read("tests/testdata/consistency/oresundHD.dfs2") x = 350000 y = 6145000 diff --git a/tests/test_dfsu.py b/tests/test_dfsu.py index 4d01dc0ec..d4e8b5a02 100644 --- a/tests/test_dfsu.py +++ b/tests/test_dfsu.py @@ -1076,7 +1076,7 @@ def test_interp_like_dataarray(tmp_path): tmp_path / "interp.dfs2" da = mikeio.read("tests/testdata/consistency/oresundHD.dfsu")[0] - da2 = mikeio.read("tests/testdata/consistency/oresundHD2.dfs2", time=[0, 1])[0] + da2 = mikeio.read("tests/testdata/consistency/oresundHD.dfs2", time=[0, 1])[0] dai = da.interp_like(da2) assert isinstance(dai, DataArray) @@ -1096,7 +1096,7 @@ def test_interp_like_dataset(tmp_path): fp = tmp_path / "interp.dfs2" ds = mikeio.read("tests/testdata/consistency/oresundHD.dfsu") - ds2 = mikeio.read("tests/testdata/consistency/oresundHD2.dfs2", time=[0, 1]) + ds2 = mikeio.read("tests/testdata/consistency/oresundHD.dfs2", time=[0, 1]) dsi = ds.interp_like(ds2) assert isinstance(dsi, Dataset) diff --git a/tests/testdata/consistency/oresundHD.dfs2 b/tests/testdata/consistency/oresundHD.dfs2 index cb141ee5808735a0e2b2ae8e97d8cc9f6c5989e8..8107abfabc4a29a87dad2533090b1222fc6def5c 100644 GIT binary patch literal 19471 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 literal 15631 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 From 76fb87cf80cd394fc354d47fde1e474f3167c9fd Mon Sep 17 00:00:00 2001 From: Henrik Andersson Date: Mon, 9 Oct 2023 09:38:07 +0200 Subject: [PATCH 08/10] Example in docs --- docs/dfs2.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/dfs2.md b/docs/dfs2.md index 8ed411dbf..3c1530816 100644 --- a/docs/dfs2.md +++ b/docs/dfs2.md @@ -16,6 +16,35 @@ items: 0: Elevation (meter) ``` +## Subset in space + +The most convenient way to subset in space is to use the `sel` method, which returns a new (smaller) dataset, which can be further processed or written to disk using the `to_dfs` method. + +```python +>>> ds.geometry + +x: [12.2, 12.21, ..., 13.1] (nx=216, dx=0.004167) +y: [55.2, 55.21, ..., 56.3] (ny=264, dy=0.004167) +projection: LONG/LAT +>>> ds_aoi = ds.sel(x=slice(12.5, 13.0), y=slice(55.5, 56.0)) +>>> ds_aoi.geometry + +x: [12.5, 12.5, ..., 12.99] (nx=120, dx=0.004167) +y: [55.5, 55.5, ..., 55.99] (ny=120, dy=0.004167) +projection: LONG/LAT +``` + +In order to specify an open-ended subset (i.e. where the end of the subset is the end of the domain), use `None` as the end of the slice. + +```python +>>> ds.sel(x=slice(None,13.0)) + +dims: (time:1, y:264, x:191) +time: 2020-05-15 11:04:52 (time-invariant) +geometry: Grid2D (ny=264, nx=191) +items: + 0: Elevation (meter) +``` ## Grid2D From ebc2c98da26660bc123de9f4cc1485b1afe734de Mon Sep 17 00:00:00 2001 From: Henrik Andersson Date: Mon, 9 Oct 2023 09:57:13 +0200 Subject: [PATCH 09/10] Update version in actions --- .github/workflows/build_docs.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build_docs.yml b/.github/workflows/build_docs.yml index d8589330f..aca9fc87d 100644 --- a/.github/workflows/build_docs.yml +++ b/.github/workflows/build_docs.yml @@ -1,4 +1,4 @@ -name: Build documentation (don't publish) +`name: Build documentation (don't publish) on: push: @@ -11,9 +11,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python 3.10 - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: "3.10" From 15c63bd73cfaedecc3ca0632ecab3886826ff526 Mon Sep 17 00:00:00 2001 From: Henrik Andersson Date: Mon, 9 Oct 2023 10:58:35 +0200 Subject: [PATCH 10/10] Update build_docs.yml --- .github/workflows/build_docs.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build_docs.yml b/.github/workflows/build_docs.yml index aca9fc87d..38a48fb69 100644 --- a/.github/workflows/build_docs.yml +++ b/.github/workflows/build_docs.yml @@ -1,8 +1,7 @@ -`name: Build documentation (don't publish) +name: Build documentation (don't publish) on: - push: - pull_request: + pull_request: branches: [ main ] jobs: @@ -30,4 +29,4 @@ jobs: uses: actions/upload-artifact@v2 with: name: html - path: docs/_build/html \ No newline at end of file + path: docs/_build/html