From aa943e9bc4cc3e65fbe0f80f2c916265ccd33060 Mon Sep 17 00:00:00 2001 From: Jake Faulkner Date: Fri, 21 Nov 2025 10:01:46 +1300 Subject: [PATCH 1/9] fix(types): type-checking failures --- .../generate_velocity_model_parameters.py | 2 +- workflow/scripts/merge_ts.py | 32 +++++++++---------- workflow/scripts/plan_workflow.py | 5 ++- workflow/scripts/realisation_to_srf.py | 14 +++++--- 4 files changed, 31 insertions(+), 22 deletions(-) diff --git a/workflow/scripts/generate_velocity_model_parameters.py b/workflow/scripts/generate_velocity_model_parameters.py index 5dcae8f5..5dc47581 100644 --- a/workflow/scripts/generate_velocity_model_parameters.py +++ b/workflow/scripts/generate_velocity_model_parameters.py @@ -317,7 +317,7 @@ def find_rrup_bounding_polygon( rrup = estimate_rrup( magnitude, rake, - np.mean([plane.dip for plane in fault.planes]), + fault.dip, pgv_target, ) logger = log_utils.get_logger(__name__) diff --git a/workflow/scripts/merge_ts.py b/workflow/scripts/merge_ts.py index f61beb3f..3e37f82e 100644 --- a/workflow/scripts/merge_ts.py +++ b/workflow/scripts/merge_ts.py @@ -97,23 +97,23 @@ def merge_ts_xyts( # If output doesn't exist when we os.open it, we'll get an error. output.touch() merged_fd = os.open(output, os.O_WRONLY) - + # The following type: ignores can be removed once qcore updates xyts_header: bytes = ( - top_left.x0.tobytes() - + top_left.y0.tobytes() - + top_left.z0.tobytes() - + top_left.t0.tobytes() - + top_left.nx.tobytes() - + top_left.ny.tobytes() - + top_left.nz.tobytes() - + top_left.nt.tobytes() - + top_left.dx.tobytes() - + top_left.dy.tobytes() - + top_left.hh.tobytes() - + top_left.dt.tobytes() - + top_left.mrot.tobytes() - + top_left.mlat.tobytes() - + top_left.mlon.tobytes() + top_left.x0.tobytes() # type: ignore + + top_left.y0.tobytes() # type: ignore + + top_left.z0.tobytes() # type: ignore + + top_left.t0.tobytes() # type: ignore + + top_left.nx.tobytes() # type: ignore + + top_left.ny.tobytes() # type: ignore + + top_left.nz.tobytes() # type: ignore + + top_left.nt.tobytes() # type: ignore + + top_left.dx.tobytes() # type: ignore + + top_left.dy.tobytes() # type: ignore + + top_left.hh.tobytes() # type: ignore + + top_left.dt.tobytes() # type: ignore + + top_left.mrot.tobytes() # type: ignore + + top_left.mlat.tobytes() # type: ignore + + top_left.mlon.tobytes() # type: ignore ) written = os.write(merged_fd, xyts_header) diff --git a/workflow/scripts/plan_workflow.py b/workflow/scripts/plan_workflow.py index 8e71bd21..4329347d 100644 --- a/workflow/scripts/plan_workflow.py +++ b/workflow/scripts/plan_workflow.py @@ -7,6 +7,7 @@ import dataclasses import tempfile from collections.abc import Iterable +from copy import deepcopy from enum import StrEnum from pathlib import Path, PurePath from typing import Annotated, Any, Optional, Self @@ -143,7 +144,9 @@ class Stage: @property def parent(self) -> Self: # numpydoc ignore=RT01 """Stage: the parent stage of this stage.""" - return self.__class__(self.identifier, self.event, None) + cpy = deepcopy(self) + cpy.sample = None + return cpy @property def directory(self) -> PurePath | None: # numpydoc ignore=RT01 diff --git a/workflow/scripts/realisation_to_srf.py b/workflow/scripts/realisation_to_srf.py index 4ba13b18..5f7e9225 100644 --- a/workflow/scripts/realisation_to_srf.py +++ b/workflow/scripts/realisation_to_srf.py @@ -54,7 +54,7 @@ from qcore import cli, coordinates from source_modelling import gsf, moment, rupture_propagation, srf -from source_modelling.sources import IsSource, Point +from source_modelling.sources import Fault, IsSource, Point from workflow import log_utils, realisations, utils from workflow.log_utils import log_call from workflow.realisations import ( @@ -417,8 +417,11 @@ def generate_fault_srf( resolution = params.srf_config.resolution - nx = sum(round(plane.length / resolution) for plane in fault.planes) - ny = round(fault.planes[0].width / resolution) + if isinstance(fault, Fault): + nx = sum(round(plane.length / resolution) for plane in fault.planes) + else: + nx = round(fault.length / resolution) + ny = round(fault.width / resolution) gsf_file_path = generate_fault_gsf( name, @@ -560,7 +563,10 @@ def generate_point_source_srf( # divide by 1000 to convert depth from meters to kilometers source_depth_km = params.source_config.source_geometries[name].centroid[2] / 1000 - fault_area_km2 = (params.source_config.source_geometries[name].length_m / 1000) ** 2 + fault_area_km2 = ( + params.source_config.source_geometries[name].length + * params.source_config.source_geometries[name].width + ) slip = moment.point_source_slip( moment_newton_metre, fault_area_km2, velocity_model_df, source_depth_km From 8c2c71740af4eff72ae80b2369384f6f138604ec Mon Sep 17 00:00:00 2001 From: Jake Faulkner Date: Fri, 21 Nov 2025 10:06:14 +1300 Subject: [PATCH 2/9] fix(plan_workflow): use idiomatic dataclasses.replace --- workflow/scripts/plan_workflow.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/workflow/scripts/plan_workflow.py b/workflow/scripts/plan_workflow.py index 4329347d..503bfcde 100644 --- a/workflow/scripts/plan_workflow.py +++ b/workflow/scripts/plan_workflow.py @@ -144,9 +144,7 @@ class Stage: @property def parent(self) -> Self: # numpydoc ignore=RT01 """Stage: the parent stage of this stage.""" - cpy = deepcopy(self) - cpy.sample = None - return cpy + return dataclasses.replace(self, sample=None) @property def directory(self) -> PurePath | None: # numpydoc ignore=RT01 From c61b7347e8e7291a26f38b2c756d6c65f360f1fe Mon Sep 17 00:00:00 2001 From: Jake Faulkner Date: Fri, 21 Nov 2025 10:06:53 +1300 Subject: [PATCH 3/9] fix(plan_workflow): ruff fixes --- workflow/scripts/plan_workflow.py | 1 - 1 file changed, 1 deletion(-) diff --git a/workflow/scripts/plan_workflow.py b/workflow/scripts/plan_workflow.py index 503bfcde..27df5fb5 100644 --- a/workflow/scripts/plan_workflow.py +++ b/workflow/scripts/plan_workflow.py @@ -7,7 +7,6 @@ import dataclasses import tempfile from collections.abc import Iterable -from copy import deepcopy from enum import StrEnum from pathlib import Path, PurePath from typing import Annotated, Any, Optional, Self From cb28207c54eff679c1cf8fdf1cbabed596442444 Mon Sep 17 00:00:00 2001 From: Jake Faulkner Date: Fri, 21 Nov 2025 13:37:27 +1300 Subject: [PATCH 4/9] fix(types): --- workflow/scripts/bb_sim.py | 1 + .../generate_velocity_model_parameters.py | 14 ++++++----- workflow/scripts/import_realisation.py | 24 +++++++++---------- workflow/scripts/nshm2022_to_realisation.py | 2 +- 4 files changed, 22 insertions(+), 19 deletions(-) diff --git a/workflow/scripts/bb_sim.py b/workflow/scripts/bb_sim.py index 9006a2fd..bcb71200 100644 --- a/workflow/scripts/bb_sim.py +++ b/workflow/scripts/bb_sim.py @@ -181,6 +181,7 @@ def combine_hf_and_lf( vs30_df["pga"] = np.abs(temp_hf_padded).max(axis=1) * G + assert isinstance(vs30_df, pd.DataFrame) hf_amp_val = siteamp_models.cb_amp_multi(vs30_df) hf_amp_fas_vals = siteamp_models.cb2014_to_fas_amplification_factors( hf_amp_val, bb_dt, bb_nt diff --git a/workflow/scripts/generate_velocity_model_parameters.py b/workflow/scripts/generate_velocity_model_parameters.py index 5dc47581..c5273dd7 100644 --- a/workflow/scripts/generate_velocity_model_parameters.py +++ b/workflow/scripts/generate_velocity_model_parameters.py @@ -278,11 +278,13 @@ def estimate_rrup( >>> estimate_rrup(7.5, 90, 45, 10) 60.86630588572306 """ - return sp.optimize.minimize_scalar( - lambda rrup: np.abs(pgv_from_rrup(magnitude, rake, dip, rrup) - pgv_target), - bounds=(0, 1000), - method="bounded", - ).x + return float( + sp.optimize.minimize_scalar( + lambda rrup: np.abs(pgv_from_rrup(magnitude, rake, dip, rrup) - pgv_target), + bounds=(0, 1000), + method="bounded", + ).x + ) def find_rrup_bounding_polygon( @@ -465,7 +467,7 @@ def generate_velocity_model_parameters( model_domain = bounding_box.minimum_area_bounding_box_for_polygons_masked( must_include=fault_buffer_polygons, may_include=rrup_bounding_polygons, - mask=utils.get_nz_outline_polygon(), + mask=utils.get_nz_outline_polygon(), # type: ignore ) sim_duration = estimate_simulation_duration( diff --git a/workflow/scripts/import_realisation.py b/workflow/scripts/import_realisation.py index a495af30..0568ab7c 100644 --- a/workflow/scripts/import_realisation.py +++ b/workflow/scripts/import_realisation.py @@ -52,24 +52,24 @@ def convert_realisation( Defaults version to use for the new realisation. """ old_realisation = pd.read_csv(old_realisation_path).iloc[0] - name = old_realisation["name"] + name = old_realisation["name"].item() metadata = RealisationMetadata( name=name, version="1", defaults_version=defaults_version ) planes: list[Plane] = [] - dip = old_realisation["dip"] - dip_dir = old_realisation["dip_dir"] - for i in range(old_realisation["plane_count"]): + dip = old_realisation["dip"].item() + dip_dir = old_realisation["dip_dir"].item() + for i in range(old_realisation["plane_count"].item()): centroid = np.array( [ - old_realisation[f"clat_subfault_{i}"], - old_realisation[f"clon_subfault_{i}"], + old_realisation[f"clat_subfault_{i}"].item(), + old_realisation[f"clon_subfault_{i}"].item(), ] ) - strike = old_realisation[f"strike_subfault_{i}"] - dtop = old_realisation[f"dtop_subfault_{i}"] - length = old_realisation[f"length_subfault_{i}"] - width = old_realisation[f"width_subfault_{i}"] + strike = old_realisation[f"strike_subfault_{i}"].item() + dtop = old_realisation[f"dtop_subfault_{i}"].item() + length = old_realisation[f"length_subfault_{i}"].item() + width = old_realisation[f"width_subfault_{i}"].item() planes.append( Plane.from_centroid_strike_dip( centroid, dip, length, width, dtop=dtop, strike=strike, dip_dir=dip_dir @@ -78,8 +78,8 @@ def convert_realisation( fault = Fault(planes) shypo = old_realisation["shypo"] / fault.length + 1 / 2 dhypo = old_realisation["dhypo"] / fault.width - magnitudes = Magnitudes({name: old_realisation["magnitude"]}) - rakes = Rakes({name: old_realisation["rake"]}) + magnitudes = Magnitudes({name: old_realisation["magnitude"].item()}) + rakes = Rakes({name: old_realisation["rake"].item()}) sources = SourceConfig(source_geometries={name: fault}) rupture_propagation_config = RupturePropagationConfig( rupture_causality_tree={name: None}, # Trivial rupture propagation tree diff --git a/workflow/scripts/nshm2022_to_realisation.py b/workflow/scripts/nshm2022_to_realisation.py index b75c00c1..76029335 100755 --- a/workflow/scripts/nshm2022_to_realisation.py +++ b/workflow/scripts/nshm2022_to_realisation.py @@ -92,7 +92,7 @@ def a_to_mw_leonard(area: float, rake: float) -> float: def default_magnitude_estimation( faults: dict[str, Fault], - components: DisjointSet, + components: DisjointSet[str], avg_rake: float, ) -> dict[str, float]: """Estimate the magnitudes for a set of faults based on their areas and average rake. From a3613ae76d27bcd1ccb0cd0c1a84358e23d683a3 Mon Sep 17 00:00:00 2001 From: Jake Faulkner Date: Fri, 21 Nov 2025 15:54:08 +1300 Subject: [PATCH 5/9] fix: test with typing stub packages --- .github/workflows/deptry.yml | 4 ++-- pyproject.toml | 1 + workflow/scripts/realisation_to_srf.py | 18 ++++++++++-------- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/.github/workflows/deptry.yml b/.github/workflows/deptry.yml index 5874abe0..d6811d21 100644 --- a/.github/workflows/deptry.yml +++ b/.github/workflows/deptry.yml @@ -15,6 +15,6 @@ jobs: # Install deptry - run: uv venv - - run: uv pip install -e ".[test,dev]" + - run: uv pip install -e "." --all-extras # Run deptry to check that all dependencies are present. - - run: uv run deptry . + - run: uv run deptry . -ddg test,dev,types diff --git a/pyproject.toml b/pyproject.toml index aa466bfa..8553dcd2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,7 @@ dependencies = [ [project.optional-dependencies] test = ["pytest"] +types = ["pandas-stubs", "types-geopandas", "types-requests", "scipy-stubs"] dev = ["ruff", "deptry", "ty"] [tool.deptry] diff --git a/workflow/scripts/realisation_to_srf.py b/workflow/scripts/realisation_to_srf.py index 5f7e9225..1dfbd8e2 100644 --- a/workflow/scripts/realisation_to_srf.py +++ b/workflow/scripts/realisation_to_srf.py @@ -327,18 +327,20 @@ def process_fault(fault_name: str) -> None: process_fault(fault_name) # Combine SRF file components + combined_slipt_array = concatenate_slip_values( + ( + fault_srf.slipt1_array + if fault_srf.slipt1_array is not None + else csr_array((len(fault_srf.points), 1)) + ) + for fault_srf in srf_file_map.values() + ) + assert combined_slipt_array is not None combined_srf = srf.SrfFile( version="1.0", header=pd.concat([fault_srf.header for fault_srf in srf_file_map.values()]), points=pd.concat([fault_srf.points for fault_srf in srf_file_map.values()]), - slipt1_array=concatenate_slip_values( - ( - fault_srf.slipt1_array - if fault_srf.slipt1_array is not None - else csr_array((len(fault_srf.points), 1)) - ) - for fault_srf in srf_file_map.values() - ), + slipt1_array=combined_slipt_array, ) # Write the combined SRF file From 845d9cc158a4629fddc692d8a28ce0cb66e1f516 Mon Sep 17 00:00:00 2001 From: Jake Faulkner Date: Fri, 21 Nov 2025 16:04:51 +1300 Subject: [PATCH 6/9] ci: fix deptry install --- .github/workflows/deptry.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deptry.yml b/.github/workflows/deptry.yml index d6811d21..d3e63efa 100644 --- a/.github/workflows/deptry.yml +++ b/.github/workflows/deptry.yml @@ -15,6 +15,6 @@ jobs: # Install deptry - run: uv venv - - run: uv pip install -e "." --all-extras + - run: uv sync --all-extras --dev # Run deptry to check that all dependencies are present. - run: uv run deptry . -ddg test,dev,types From 985854abb090552e4522378a8c433020577b0851 Mon Sep 17 00:00:00 2001 From: Jake Faulkner Date: Fri, 21 Nov 2025 16:13:29 +1300 Subject: [PATCH 7/9] fix(nshm2022-to-realisation): use deferred annotation for `default_magnitude_estimation` --- workflow/scripts/nshm2022_to_realisation.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/workflow/scripts/nshm2022_to_realisation.py b/workflow/scripts/nshm2022_to_realisation.py index 76029335..c1e0e872 100755 --- a/workflow/scripts/nshm2022_to_realisation.py +++ b/workflow/scripts/nshm2022_to_realisation.py @@ -92,7 +92,9 @@ def a_to_mw_leonard(area: float, rake: float) -> float: def default_magnitude_estimation( faults: dict[str, Fault], - components: DisjointSet[str], + # NOTE: this must be in quotes because the runtime class DisjointSet is + # not generic, just the stub implementation. + components: "DisjointSet[str]", avg_rake: float, ) -> dict[str, float]: """Estimate the magnitudes for a set of faults based on their areas and average rake. From f572768380fb35d1a8f3e118ee56dab34d38609c Mon Sep 17 00:00:00 2001 From: Jake Faulkner Date: Mon, 24 Nov 2025 11:44:04 +1300 Subject: [PATCH 8/9] fix(nshm2022_to_realisation): ignore scipy stubs bug --- workflow/scripts/nshm2022_to_realisation.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/workflow/scripts/nshm2022_to_realisation.py b/workflow/scripts/nshm2022_to_realisation.py index c1e0e872..7c3390cf 100755 --- a/workflow/scripts/nshm2022_to_realisation.py +++ b/workflow/scripts/nshm2022_to_realisation.py @@ -120,7 +120,8 @@ def default_magnitude_estimation( estimated_moment = mag_scaling.mag2mom(estimated_mw) roots = {components[fault_name] for fault_name in faults} component_areas = { - root: sum(faults[name].area() for name in components.subset(root)) + # TODO: Remove type ignore after scipy stubs 1.16.3.1 + root: sum(faults[name].area() for name in components.subset(root)) # type: ignore[invalid-argument-type] for root in roots } total_component_area = sum( From d5c72113e67a0cbdb8184075e019738941088a77 Mon Sep 17 00:00:00 2001 From: Jake Faulkner Date: Tue, 25 Nov 2025 10:30:33 +1300 Subject: [PATCH 9/9] Revert "fix(nshm2022_to_realisation): ignore scipy stubs bug" This reverts commit f572768380fb35d1a8f3e118ee56dab34d38609c. --- workflow/scripts/nshm2022_to_realisation.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/workflow/scripts/nshm2022_to_realisation.py b/workflow/scripts/nshm2022_to_realisation.py index 7c3390cf..c1e0e872 100755 --- a/workflow/scripts/nshm2022_to_realisation.py +++ b/workflow/scripts/nshm2022_to_realisation.py @@ -120,8 +120,7 @@ def default_magnitude_estimation( estimated_moment = mag_scaling.mag2mom(estimated_mw) roots = {components[fault_name] for fault_name in faults} component_areas = { - # TODO: Remove type ignore after scipy stubs 1.16.3.1 - root: sum(faults[name].area() for name in components.subset(root)) # type: ignore[invalid-argument-type] + root: sum(faults[name].area() for name in components.subset(root)) for root in roots } total_component_area = sum(