diff --git a/pyproject.toml b/pyproject.toml index 7b706a7..bbf2b70 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,6 @@ dependencies = [ "oq_wrapper>=2025.12.3", "qcore-utils>=2025.12.1", "source_modelling>=2025.12.1", - # Data Formats "geopandas", "pandas[parquet, hdf5]", @@ -29,20 +28,22 @@ dependencies = [ "numpy", "scipy", "shapely", - # CLI "tqdm", "typer", - # Misc. "requests", # For gcmt-to-realisation "schema", # For loading realisations "structlog", # Logging. "psutil", # To get the CPU affinity for jobs + ] [project.optional-dependencies] -test = ["pytest"] +test = [ + "pytest>=6.0.0", # required for the tool.pytest section + "hypothesis[numpy]>=6.0.0", +] types = ["pandas-stubs", "types-geopandas", "types-requests", "scipy-stubs"] dev = ["ruff", "deptry", "ty", "numpydoc"] @@ -53,7 +54,7 @@ pep621_dev_dependency_groups = ["test", "dev"] nshm2022-to-realisation = "workflow.scripts.nshm2022_to_realisation:app" gcmt-to-realisation = "workflow.scripts.gcmt_to_realisation:app" realisation-to-srf = "workflow.scripts.realisation_to_srf:app" -generate-velocity-model-parameters = "workflow.scripts.generate_velocity_model_parameters:app" +generate-domain = "workflow.scripts.generate_domain:app" generate-velocity-model = "workflow.scripts.generate_velocity_model:app" generate-station-coordinates = "workflow.scripts.generate_station_coordinates:app" generate-model-coordinates = "workflow.scripts.generate_model_coordinates:app" @@ -76,6 +77,8 @@ workflow = "workflow" [tool.setuptools_scm] +[tool.pytest] +markers = ["slow: mark test as slow."] [tool.ruff.lint] extend-select = [ diff --git a/tests/test_cli.py b/tests/test_cli.py index 4b46647..a36c2a1 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,60 +1,59 @@ -from collections.abc import Callable +import importlib +import pkgutil +from types import ModuleType -import pytest +from pytest import Metafunc from typer import Typer from typer.testing import CliRunner -from workflow.scripts import ( - bb_sim, - check_domain, - check_srf, - copy_velocity_model_parameters, - create_e3d_par, - gcmt_auto_simulate, - gcmt_to_realisation, - generate_rupture_propagation, - generate_station_coordinates, - generate_stoch, - generate_velocity_model, - generate_velocity_model_parameters, - hf_sim, - im_calc, - import_realisation, - lf_to_xarray, - nshm2022_to_realisation, - realisation_to_srf, -) - - -@pytest.mark.parametrize( - "script", - [ - bb_sim, - check_domain, - check_srf, - copy_velocity_model_parameters, - create_e3d_par, - gcmt_auto_simulate, - gcmt_to_realisation, - generate_rupture_propagation, - generate_station_coordinates, - generate_stoch, - generate_velocity_model, - generate_velocity_model_parameters, - lf_to_xarray, - hf_sim, - im_calc, - import_realisation, - nshm2022_to_realisation, - realisation_to_srf, - ], -) -def test_invocation_of_script(script: Callable) -> None: - """Basic check that the scripts can be invoked.""" +import workflow.scripts as scripts_package + +EXCLUDE_MODULES = set() + + +def collect_script_modules() -> list[ModuleType]: + """ + Dynamically discovers all modules within the workflow.scripts package. + """ + modules = [] + # Iterates through all modules in the package directory + for loader, module_name, is_pkg in pkgutil.iter_modules(scripts_package.__path__): + if module_name in EXCLUDE_MODULES: + continue + + # Construct full module path (e.g., workflow.scripts.bb_sim) + full_module_name = f"{scripts_package.__name__}.{module_name}" + module = importlib.import_module(full_module_name) + modules.append(module) + return modules + + +def pytest_generate_tests(metafunc: Metafunc) -> None: + """ + Generate tests dynamically based on discovered modules. + """ + if "script_module" in metafunc.fixturenames: + found_modules = collect_script_modules() + # Create readable IDs from the module names (e.g., 'bb_sim') + ids = [m.__name__.split(".")[-1] for m in found_modules] + metafunc.parametrize("script_module", found_modules, ids=ids) + + +def test_invocation_of_script(script_module: ModuleType) -> None: + """ + Test that each discovered module has a Typer app and responds to --help. + """ runner = CliRunner() - # The following satisifies the type checker. - app = getattr(script, "app", None) - assert isinstance(app, Typer) + + assert hasattr(script_module, "app"), ( + f"Module {script_module.__name__} is missing the 'app' attribute." + ) + + app = getattr(script_module, "app") + + assert isinstance(app, Typer), ( + f"'app' in {script_module.__name__} should be a Typer instance, but got {type(app)}." + ) result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 diff --git a/tests/test_generate_domain.py b/tests/test_generate_domain.py new file mode 100644 index 0000000..54c2967 --- /dev/null +++ b/tests/test_generate_domain.py @@ -0,0 +1,112 @@ +from types import SimpleNamespace + +import numpy as np +import pytest +import shapely +from hypothesis import given +from hypothesis import strategies as st + +from source_modelling import sources +from workflow.realisations import ( + Magnitudes, + Rakes, + SourceConfig, + VelocityModelParameters, +) +from workflow.scripts import generate_domain + + +# Slow because of openquake import +@pytest.mark.slow +def test_significant_duration_calculation() -> None: + """Basic integration test for OQW, tests that the interface works as we expect still.""" + ds595 = generate_domain.get_significant_duration( + magnitude=6.5, distance=100.0, vs30=500.0, rake=180.0, z1pt0=5.0 + ) + assert isinstance(ds595, float) # Should not be lying about the type + assert np.isfinite(ds595), "Ds595 is invalid" + # Check that ds595 is not in log-space or similar + assert ds595 > 0, "Ds595 should be positive" + assert ds595 < 1000, "Ds595 unrealistically high" + # Combined with type checking this should be enough to catch most of these problems. + + +def test_simulation_max_depth_increases_with_mw() -> None: + depth_mw5p0 = generate_domain.simulation_max_depth(magnitude=5.0, bottom_depth=10.0) + depth_mw7p0 = generate_domain.simulation_max_depth(magnitude=6.0, bottom_depth=10.0) + depth_mw9p0 = generate_domain.simulation_max_depth(magnitude=9.0, bottom_depth=10.0) + assert depth_mw5p0 < depth_mw7p0 < depth_mw9p0, ( + "Depths do not increase with magnitude" + ) + + +@given( + magnitude=st.floats(min_value=3.5, max_value=9.0), + depth=st.floats(min_value=1.0, max_value=250), +) +def test_simulation_max_depth_more_than_bottom_depth( + magnitude: float, depth: float +) -> None: + simulation_depth = generate_domain.simulation_max_depth(magnitude, depth) + assert np.isfinite(simulation_depth), "Invalid simulation depth" + assert depth <= simulation_depth <= 350, "Sensible simulation depths are applied" + + +def test_estimate_domain_contains_fault_geometry() -> None: + fault_coords = [(100000, 100000), (110000, 100000)] + fault_geom = shapely.LineString(fault_coords) + + mock_fault = SimpleNamespace(geometry=fault_geom) + source_config = SimpleNamespace(source_geometries={"fault_a": mock_fault}) + + rrups = {"fault_a": 5000.0} + + nz_outline = shapely.box(0, 0, 500000, 500000) + + result_domain = generate_domain.estimate_domain( + source_config=source_config, # type: ignore[invalid-argument-type] + rrups=rrups, + nz_outline=nz_outline, + fault_buffer=2000.0, + ) + + assert result_domain.polygon.contains(fault_geom), ( + f"Domain polygon should contain the original fault geometry.\n" + f"Fault bounds: {fault_geom.bounds}\n" + f"Domain bounds: {result_domain.polygon.bounds}" + ) + + +# Slow because of openquake import +@pytest.mark.slow +def test_generate_domain() -> None: + """Basic E2E test to check that domain generation works without crashing or producing a silly domain.""" + source = sources.Point( + np.array([-43.0, -172.0, 10000.0]), + length_m=1000, + width_m=1000, + strike=90.0, + dip=45.0, + dip_dir=180.0, + ) + source_config = SourceConfig(dict(source=source)) + magnitudes = Magnitudes(dict(source=6.0)) + rakes = Rakes(dict(source=180.0)) + + velocity_model_parameters = VelocityModelParameters( + min_vs=500.0, + version="2.09", + topo_type="BULLDOZED", + ds_multiplier=1.2, + vs30=500.0, + fault_buffer=2000, + s_wave_velocity=3500, + rrup_interpolants=np.array([[5.0, 8.0], [50.0, 50.0]]), + ) + domain_parameters = generate_domain.generate_domain( + source_config, + magnitudes, + rakes, + velocity_model_parameters, + ) + assert shapely.contains(domain_parameters.domain.polygon, source.geometry) diff --git a/tests/test_realisation.py b/tests/test_realisation.py index 5f45398..e1d4051 100644 --- a/tests/test_realisation.py +++ b/tests/test_realisation.py @@ -245,8 +245,9 @@ def test_velocity_model(tmp_path: Path) -> None: topo_type="SQUASHED_TAPERED", ds_multiplier=1.2, vs30=300.0, + fault_buffer=2000.0, s_wave_velocity=3500.0, - pgv_interpolants=np.ones(shape=(2, 2), dtype=np.float32), + rrup_interpolants=np.ones(shape=(2, 2), dtype=np.float32), ) realisation_ffp = tmp_path / "realisation.json" velocity_model.write_to_realisation(realisation_ffp) @@ -258,8 +259,9 @@ def test_velocity_model(tmp_path: Path) -> None: "topo_type": "SQUASHED_TAPERED", "ds_multiplier": 1.2, "vs30": 300.0, + "fault_buffer": 2000.0, "s_wave_velocity": 3500.0, - "pgv_interpolants": [[1, 1], [1, 1]], + "rrup_interpolants": [[1, 1], [1, 1]], } } diff --git a/tests/test_utils.py b/tests/test_utils.py index 19819ed..63b1430 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -45,6 +45,7 @@ def test_raises_when_no_cpu_info() -> None: utils.get_available_cores() +@pytest.mark.slow def test_read_nz_coastline() -> None: gdf = utils.read_nz_coastline() assert isinstance(gdf, gpd.GeoDataFrame) @@ -80,3 +81,64 @@ def test_get_nz_outline_polygon_selects_two_largest() -> None: expected_union = shapely.transform(expected_union, lambda x: x[:, ::-1]) # Check the result is equal to the union of the two largest polygons assert shapely.area(shapely.symmetric_difference(result, expected_union)) < 1e-4 + + +def test_dict_zip_basic_two_dicts() -> None: + """Test standard zipping of two dictionaries with matching keys.""" + d1 = {"a": 1, "b": 2} + d2 = {"a": "apple", "b": "banana"} + + expected = {"a": (1, "apple"), "b": (2, "banana")} + assert utils.dict_zip(d1, d2) == expected + + +def test_dict_zip_three_dicts() -> None: + """Test standard zipping of three dictionaries.""" + d1 = {"a": 1} + d2 = {"a": 2} + d3 = {"a": 3} + + assert utils.dict_zip(d1, d2, d3) == {"a": (1, 2, 3)} + + +def test_dict_zip_strict_mismatch_raises_error() -> None: + """Test that strict=True raises ValueError when keys don't match exactly.""" + d1 = {"a": 1, "b": 2} + d2 = {"a": 1} # Missing 'b' + + with pytest.raises(ValueError, match="Keys in dictionaries are not all the same"): + utils.dict_zip(d1, d2, strict=True) + + +def test_dict_zip_non_strict_intersection() -> None: + """Test that strict=False returns the intersection of keys.""" + d1 = {"a": 1, "b": 2, "c": 3} + d2 = {"a": 10, "b": 20, "d": 40} + + # Only 'a' and 'b' are in both + result = utils.dict_zip(d1, d2, strict=False) + assert set(result.keys()) == {"a", "b"} + assert result["a"] == (1, 10) + assert result["b"] == (2, 20) + + +def test_dict_zip_empty_input() -> None: + """Test behaviour with no dictionaries provided.""" + assert utils.dict_zip() == {} + + +def test_dict_zip_single_dict() -> None: + """Test behaviour with a single dictionary.""" + d1 = {"a": 1, "b": 2} + assert utils.dict_zip(d1) == {"a": (1,), "b": (2,)} + + +def test_dict_zip_identical_keys_different_order() -> None: + """Test that key order in input doesn't cause strict mode to fail.""" + d1 = {"a": 1, "b": 2} + d2 = {"b": 20, "a": 10} + + # Should not raise ValueError even though insertion order differs + result = utils.dict_zip(d1, d2, strict=True) + assert result["a"] == (1, 10) + assert result["b"] == (2, 20) diff --git a/uv.lock b/uv.lock index 3bdac09..324bd54 100644 --- a/uv.lock +++ b/uv.lock @@ -560,11 +560,11 @@ wheels = [ [[package]] name = "filelock" -version = "3.20.2" +version = "3.20.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c1/e0/a75dbe4bca1e7d41307323dad5ea2efdd95408f74ab2de8bd7dba9b51a1a/filelock-3.20.2.tar.gz", hash = "sha256:a2241ff4ddde2a7cebddf78e39832509cb045d18ec1a09d7248d6bfc6bfbbe64", size = 19510, upload-time = "2026-01-02T15:33:32.582Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/65/ce7f1b70157833bf3cb851b556a37d4547ceafc158aa9b34b36782f23696/filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1", size = 19485, upload-time = "2026-01-09T17:55:05.421Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/30/ab407e2ec752aa541704ed8f93c11e2a5d92c168b8a755d818b74a3c5c2d/filelock-3.20.2-py3-none-any.whl", hash = "sha256:fbba7237d6ea277175a32c54bb71ef814a8546d8601269e1bfc388de333974e8", size = 16697, upload-time = "2026-01-02T15:33:31.133Z" }, + { url = "https://files.pythonhosted.org/packages/b5/36/7fb70f04bf00bc646cd5bb45aa9eddb15e19437a28b8fb2b4a5249fac770/filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1", size = 16701, upload-time = "2026-01-09T17:55:04.334Z" }, ] [[package]] @@ -641,11 +641,11 @@ wheels = [ [[package]] name = "fsspec" -version = "2025.12.0" +version = "2026.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b6/27/954057b0d1f53f086f681755207dda6de6c660ce133c829158e8e8fe7895/fsspec-2025.12.0.tar.gz", hash = "sha256:c505de011584597b1060ff778bb664c1bc022e87921b0e4f10cc9c44f9635973", size = 309748, upload-time = "2025-12-03T15:23:42.687Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/7d/5df2650c57d47c57232af5ef4b4fdbff182070421e405e0d62c6cdbfaa87/fsspec-2026.1.0.tar.gz", hash = "sha256:e987cb0496a0d81bba3a9d1cee62922fb395e7d4c3b575e57f547953334fe07b", size = 310496, upload-time = "2026-01-09T15:21:35.562Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/c7/b64cae5dba3a1b138d7123ec36bb5ccd39d39939f18454407e5468f4763f/fsspec-2025.12.0-py3-none-any.whl", hash = "sha256:8bf1fe301b7d8acfa6e8571e3b1c3d158f909666642431cc78a1b7b4dbc5ec5b", size = 201422, upload-time = "2025-12-03T15:23:41.434Z" }, + { url = "https://files.pythonhosted.org/packages/01/c9/97cc5aae1648dcb851958a3ddf73ccd7dbe5650d95203ecb4d7720b4cdbf/fsspec-2026.1.0-py3-none-any.whl", hash = "sha256:cb76aa913c2285a3b49bdd5fc55b1d7c708d7208126b60f2eb8194fe1b4cbdcc", size = 201838, upload-time = "2026-01-09T15:21:34.041Z" }, ] [[package]] @@ -734,14 +734,14 @@ wheels = [ [[package]] name = "hypothesis" -version = "6.149.1" +version = "6.150.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sortedcontainers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c0/8d/b332c373c2571996d33b8721f3b35788fac8dc3770fe7958f639f2a0f24e/hypothesis-6.149.1.tar.gz", hash = "sha256:abe36199df3f068f72db85bd5f347a9032b79044a27bd9d2eb016179e5313069", size = 474274, upload-time = "2026-01-05T22:31:06.476Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d2/19/a4eee0c98e2ec678854272f79646f34943f8fbbc42689cc355b530c5bc96/hypothesis-6.150.2.tar.gz", hash = "sha256:deb043c41c53eaf0955f4a08739c2a34c3d8040ee3d9a2da0aa5470122979f75", size = 475250, upload-time = "2026-01-13T17:09:22.146Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/de/49d680ea2e43237a06adadce52cc1f7042551295240a3c7eaeaca9f8c009/hypothesis-6.149.1-py3-none-any.whl", hash = "sha256:48d7ea77cae8b83c6c3c9f50ac683ae3b1673c1c306909410ea348e4e97aeb77", size = 541691, upload-time = "2026-01-05T22:31:03.853Z" }, + { url = "https://files.pythonhosted.org/packages/b3/5e/21caad4acf45db7caf730cca1bc61422283e4c4e841efbc862d17ab81a21/hypothesis-6.150.2-py3-none-any.whl", hash = "sha256:648d6a2be435889e713ba3d335b0fb5e7a250f569b56e6867887c1e7a0d1f02f", size = 542712, upload-time = "2026-01-13T17:09:19.945Z" }, ] [package.optional-dependencies] @@ -1286,7 +1286,7 @@ wheels = [ [[package]] name = "openquake-engine" -version = "3.20.1" +version = "3.24.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alpha-shapes" }, @@ -1295,6 +1295,7 @@ dependencies = [ { name = "docutils" }, { name = "h5py" }, { name = "matplotlib" }, + { name = "numba" }, { name = "numpy" }, { name = "pandas" }, { name = "psutil" }, @@ -1306,9 +1307,9 @@ dependencies = [ { name = "shapely" }, { name = "toml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e0/72/851a882224b289269b865e60ea37657d56f492312c1a16c83db93b75e4b0/openquake_engine-3.20.1.tar.gz", hash = "sha256:81d344b3289720f6c566de079c2dc4dfc7f5b6aae4c94d1ebd0bec8a7908aa0f", size = 53187742, upload-time = "2024-06-07T09:58:22.743Z" } +sdist = { url = "https://files.pythonhosted.org/packages/12/f3/8fe75632649c5314b9205eade5e7bfa84e75b4b0b4545262919fe39c4f8b/openquake_engine-3.24.1.tar.gz", hash = "sha256:6a7b09c446227076d946b40f7e25a6fb1cd559a53b11eeeeda01ed29ab0c6f14", size = 58390940, upload-time = "2025-11-21T18:04:59.333Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/7f/088567f3bd835b429a09b88071733b50e88e46b4433837bc27c3d670fa81/openquake.engine-3.20.1-py3-none-any.whl", hash = "sha256:4dad0f314a4c796223c89af0562be609f9d999eda5922e4965e33396ce02d2d1", size = 54882909, upload-time = "2024-06-07T09:58:15.609Z" }, + { url = "https://files.pythonhosted.org/packages/73/d8/e416912044e400c439cdb1f85ce7ea8bcb65fd4c06798838f66ee6da0162/openquake_engine-3.24.1-py3-none-any.whl", hash = "sha256:3d75e68cecb6d944b3b28f2384aed71a32b8e4bae9b1c2e4112220a7909c5858", size = 60226848, upload-time = "2025-11-21T18:04:53.104Z" }, ] [[package]] @@ -1402,15 +1403,15 @@ pyarrow = [ [[package]] name = "pandas-stubs" -version = "2.3.3.251201" +version = "2.3.3.260113" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, { name = "types-pytz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ee/a6/491b2af2cb3ee232765a73fb273a44cc1ac33b154f7745b2df2ee1dc4d01/pandas_stubs-2.3.3.251201.tar.gz", hash = "sha256:7a980f4f08cff2a6d7e4c6d6d26f4c5fcdb82a6f6531489b2f75c81567fe4536", size = 107787, upload-time = "2025-12-01T18:29:22.403Z" } +sdist = { url = "https://files.pythonhosted.org/packages/92/5d/be23854a73fda69f1dbdda7bc10fbd6f930bd1fa87aaec389f00c901c1e8/pandas_stubs-2.3.3.260113.tar.gz", hash = "sha256:076e3724bcaa73de78932b012ec64b3010463d377fa63116f4e6850643d93800", size = 116131, upload-time = "2026-01-13T22:30:16.704Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/68/78a3c253f146254b8e2c19f4a4768f272e12ef11001d9b45ec7b165db054/pandas_stubs-2.3.3.251201-py3-none-any.whl", hash = "sha256:eb5c9b6138bd8492fd74a47b09c9497341a278fcfbc8633ea4b35b230ebf4be5", size = 164638, upload-time = "2025-12-01T18:29:21.006Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c6/df1fe324248424f77b89371116dab5243db7f052c32cc9fe7442ad9c5f75/pandas_stubs-2.3.3.260113-py3-none-any.whl", hash = "sha256:ec070b5c576e1badf12544ae50385872f0631fc35d99d00dc598c2954ec564d3", size = 168246, upload-time = "2026-01-13T22:30:15.244Z" }, ] [[package]] @@ -1424,11 +1425,11 @@ wheels = [ [[package]] name = "pathspec" -version = "1.0.0" +version = "1.0.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c2/97/39352be14d20d377a387828daf9d3f765fad1ff29bd49913d5bbf4cefe61/pathspec-1.0.0.tar.gz", hash = "sha256:9ada63a23541746b0cf7d5672a39ea77eac31dd23a80470be90df83537512131", size = 129410, upload-time = "2026-01-06T03:21:22.892Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/b2/bb8e495d5262bfec41ab5cb18f522f1012933347fb5d9e62452d446baca2/pathspec-1.0.3.tar.gz", hash = "sha256:bac5cf97ae2c2876e2d25ebb15078eb04d76e4b98921ee31c6f85ade8b59444d", size = 130841, upload-time = "2026-01-09T15:46:46.009Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/05/bb/39e6768529454cc2b57e1e2fa0a0a18ff64397a16303270e215a3e03285f/pathspec-1.0.0-py3-none-any.whl", hash = "sha256:1373719036e64a2b9de3b8ddd9e30afb082a915619f07265ed76d9ae507800ae", size = 54316, upload-time = "2026-01-06T03:21:21.74Z" }, + { url = "https://files.pythonhosted.org/packages/32/2b/121e912bd60eebd623f873fd090de0e84f322972ab25a7f9044c056804ed/pathspec-1.0.3-py3-none-any.whl", hash = "sha256:e80767021c1cc524aa3fb14bedda9c34406591343cc42797b386ce7b9354fb6c", size = 55021, upload-time = "2026-01-09T15:46:44.652Z" }, ] [[package]] @@ -1657,7 +1658,7 @@ wheels = [ [[package]] name = "pygmt" -version = "0.17.0" +version = "0.18.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, @@ -1665,9 +1666,9 @@ dependencies = [ { name = "pandas" }, { name = "xarray" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c0/6e/a973f55dbe085713a0f705561fc4214593e11a2e4d25248f6502511bec7b/pygmt-0.17.0.tar.gz", hash = "sha256:06d7581bc35641db318c418aba02163bc519ba267a5a60b5cc419eee40390503", size = 228512, upload-time = "2025-10-02T22:51:40.781Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/00/be2269bf4ccc957e391232c2f03f8bb3af7dc36b7a501b7c0dac0f2e7beb/pygmt-0.18.0.tar.gz", hash = "sha256:9b285ed422c760cc043106205cf6d67085442ecbb86aaecdaabedd8507aae740", size = 238671, upload-time = "2026-01-12T02:45:24.557Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/11/e5/e249f42f47ca6ac7f88cad10d539334b04d77a76dc9d11b58b6a72b8b702/pygmt-0.17.0-py3-none-any.whl", hash = "sha256:846f93aef999be8f9d6de806e1aeebddc70ed59c33757c16bbb236678d0f3b6c", size = 320982, upload-time = "2025-10-02T22:51:39.077Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6d/088e17906b1632c9c4b574aa42b5629e2b3228827c044557d5c721cfaf80/pygmt-0.18.0-py3-none-any.whl", hash = "sha256:fdf2434beaa0bd7891f2282fb79fc2420b0485dda9baa7e209153b110aea7b64", size = 332521, upload-time = "2026-01-12T02:45:22.879Z" }, ] [[package]] @@ -2031,28 +2032,28 @@ wheels = [ [[package]] name = "ruff" -version = "0.14.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/08/52232a877978dd8f9cf2aeddce3e611b40a63287dfca29b6b8da791f5e8d/ruff-0.14.10.tar.gz", hash = "sha256:9a2e830f075d1a42cd28420d7809ace390832a490ed0966fe373ba288e77aaf4", size = 5859763, upload-time = "2025-12-18T19:28:57.98Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/60/01/933704d69f3f05ee16ef11406b78881733c186fe14b6a46b05cfcaf6d3b2/ruff-0.14.10-py3-none-linux_armv6l.whl", hash = "sha256:7a3ce585f2ade3e1f29ec1b92df13e3da262178df8c8bdf876f48fa0e8316c49", size = 13527080, upload-time = "2025-12-18T19:29:25.642Z" }, - { url = "https://files.pythonhosted.org/packages/df/58/a0349197a7dfa603ffb7f5b0470391efa79ddc327c1e29c4851e85b09cc5/ruff-0.14.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:674f9be9372907f7257c51f1d4fc902cb7cf014b9980152b802794317941f08f", size = 13797320, upload-time = "2025-12-18T19:29:02.571Z" }, - { url = "https://files.pythonhosted.org/packages/7b/82/36be59f00a6082e38c23536df4e71cdbc6af8d7c707eade97fcad5c98235/ruff-0.14.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d85713d522348837ef9df8efca33ccb8bd6fcfc86a2cde3ccb4bc9d28a18003d", size = 12918434, upload-time = "2025-12-18T19:28:51.202Z" }, - { url = "https://files.pythonhosted.org/packages/a6/00/45c62a7f7e34da92a25804f813ebe05c88aa9e0c25e5cb5a7d23dd7450e3/ruff-0.14.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6987ebe0501ae4f4308d7d24e2d0fe3d7a98430f5adfd0f1fead050a740a3a77", size = 13371961, upload-time = "2025-12-18T19:29:04.991Z" }, - { url = "https://files.pythonhosted.org/packages/40/31/a5906d60f0405f7e57045a70f2d57084a93ca7425f22e1d66904769d1628/ruff-0.14.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16a01dfb7b9e4eee556fbfd5392806b1b8550c9b4a9f6acd3dbe6812b193c70a", size = 13275629, upload-time = "2025-12-18T19:29:21.381Z" }, - { url = "https://files.pythonhosted.org/packages/3e/60/61c0087df21894cf9d928dc04bcd4fb10e8b2e8dca7b1a276ba2155b2002/ruff-0.14.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7165d31a925b7a294465fa81be8c12a0e9b60fb02bf177e79067c867e71f8b1f", size = 14029234, upload-time = "2025-12-18T19:29:00.132Z" }, - { url = "https://files.pythonhosted.org/packages/44/84/77d911bee3b92348b6e5dab5a0c898d87084ea03ac5dc708f46d88407def/ruff-0.14.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c561695675b972effb0c0a45db233f2c816ff3da8dcfbe7dfc7eed625f218935", size = 15449890, upload-time = "2025-12-18T19:28:53.573Z" }, - { url = "https://files.pythonhosted.org/packages/e9/36/480206eaefa24a7ec321582dda580443a8f0671fdbf6b1c80e9c3e93a16a/ruff-0.14.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bb98fcbbc61725968893682fd4df8966a34611239c9fd07a1f6a07e7103d08e", size = 15123172, upload-time = "2025-12-18T19:29:23.453Z" }, - { url = "https://files.pythonhosted.org/packages/5c/38/68e414156015ba80cef5473d57919d27dfb62ec804b96180bafdeaf0e090/ruff-0.14.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f24b47993a9d8cb858429e97bdf8544c78029f09b520af615c1d261bf827001d", size = 14460260, upload-time = "2025-12-18T19:29:27.808Z" }, - { url = "https://files.pythonhosted.org/packages/b3/19/9e050c0dca8aba824d67cc0db69fb459c28d8cd3f6855b1405b3f29cc91d/ruff-0.14.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59aabd2e2c4fd614d2862e7939c34a532c04f1084476d6833dddef4afab87e9f", size = 14229978, upload-time = "2025-12-18T19:29:11.32Z" }, - { url = "https://files.pythonhosted.org/packages/51/eb/e8dd1dd6e05b9e695aa9dd420f4577debdd0f87a5ff2fedda33c09e9be8c/ruff-0.14.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:213db2b2e44be8625002dbea33bb9c60c66ea2c07c084a00d55732689d697a7f", size = 14338036, upload-time = "2025-12-18T19:29:09.184Z" }, - { url = "https://files.pythonhosted.org/packages/6a/12/f3e3a505db7c19303b70af370d137795fcfec136d670d5de5391e295c134/ruff-0.14.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b914c40ab64865a17a9a5b67911d14df72346a634527240039eb3bd650e5979d", size = 13264051, upload-time = "2025-12-18T19:29:13.431Z" }, - { url = "https://files.pythonhosted.org/packages/08/64/8c3a47eaccfef8ac20e0484e68e0772013eb85802f8a9f7603ca751eb166/ruff-0.14.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1484983559f026788e3a5c07c81ef7d1e97c1c78ed03041a18f75df104c45405", size = 13283998, upload-time = "2025-12-18T19:29:06.994Z" }, - { url = "https://files.pythonhosted.org/packages/12/84/534a5506f4074e5cc0529e5cd96cfc01bb480e460c7edf5af70d2bcae55e/ruff-0.14.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c70427132db492d25f982fffc8d6c7535cc2fd2c83fc8888f05caaa248521e60", size = 13601891, upload-time = "2025-12-18T19:28:55.811Z" }, - { url = "https://files.pythonhosted.org/packages/0d/1e/14c916087d8598917dbad9b2921d340f7884824ad6e9c55de948a93b106d/ruff-0.14.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5bcf45b681e9f1ee6445d317ce1fa9d6cba9a6049542d1c3d5b5958986be8830", size = 14336660, upload-time = "2025-12-18T19:29:16.531Z" }, - { url = "https://files.pythonhosted.org/packages/f2/1c/d7b67ab43f30013b47c12b42d1acd354c195351a3f7a1d67f59e54227ede/ruff-0.14.10-py3-none-win32.whl", hash = "sha256:104c49fc7ab73f3f3a758039adea978869a918f31b73280db175b43a2d9b51d6", size = 13196187, upload-time = "2025-12-18T19:29:19.006Z" }, - { url = "https://files.pythonhosted.org/packages/fb/9c/896c862e13886fae2af961bef3e6312db9ebc6adc2b156fe95e615dee8c1/ruff-0.14.10-py3-none-win_amd64.whl", hash = "sha256:466297bd73638c6bdf06485683e812db1c00c7ac96d4ddd0294a338c62fdc154", size = 14661283, upload-time = "2025-12-18T19:29:30.16Z" }, - { url = "https://files.pythonhosted.org/packages/74/31/b0e29d572670dca3674eeee78e418f20bdf97fa8aa9ea71380885e175ca0/ruff-0.14.10-py3-none-win_arm64.whl", hash = "sha256:e51d046cf6dda98a4633b8a8a771451107413b0f07183b2bef03f075599e44e6", size = 13729839, upload-time = "2025-12-18T19:28:48.636Z" }, +version = "0.14.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/50/0a/1914efb7903174b381ee2ffeebb4253e729de57f114e63595114c8ca451f/ruff-0.14.13.tar.gz", hash = "sha256:83cd6c0763190784b99650a20fec7633c59f6ebe41c5cc9d45ee42749563ad47", size = 6059504, upload-time = "2026-01-15T20:15:16.918Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/ae/0deefbc65ca74b0ab1fd3917f94dc3b398233346a74b8bbb0a916a1a6bf6/ruff-0.14.13-py3-none-linux_armv6l.whl", hash = "sha256:76f62c62cd37c276cb03a275b198c7c15bd1d60c989f944db08a8c1c2dbec18b", size = 13062418, upload-time = "2026-01-15T20:14:50.779Z" }, + { url = "https://files.pythonhosted.org/packages/47/df/5916604faa530a97a3c154c62a81cb6b735c0cb05d1e26d5ad0f0c8ac48a/ruff-0.14.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:914a8023ece0528d5cc33f5a684f5f38199bbb566a04815c2c211d8f40b5d0ed", size = 13442344, upload-time = "2026-01-15T20:15:07.94Z" }, + { url = "https://files.pythonhosted.org/packages/4c/f3/e0e694dd69163c3a1671e102aa574a50357536f18a33375050334d5cd517/ruff-0.14.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d24899478c35ebfa730597a4a775d430ad0d5631b8647a3ab368c29b7e7bd063", size = 12354720, upload-time = "2026-01-15T20:15:09.854Z" }, + { url = "https://files.pythonhosted.org/packages/c3/e8/67f5fcbbaee25e8fc3b56cc33e9892eca7ffe09f773c8e5907757a7e3bdb/ruff-0.14.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9aaf3870f14d925bbaf18b8a2347ee0ae7d95a2e490e4d4aea6813ed15ebc80e", size = 12774493, upload-time = "2026-01-15T20:15:20.908Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ce/d2e9cb510870b52a9565d885c0d7668cc050e30fa2c8ac3fb1fda15c083d/ruff-0.14.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac5b7f63dd3b27cc811850f5ffd8fff845b00ad70e60b043aabf8d6ecc304e09", size = 12815174, upload-time = "2026-01-15T20:15:05.74Z" }, + { url = "https://files.pythonhosted.org/packages/88/00/c38e5da58beebcf4fa32d0ddd993b63dfacefd02ab7922614231330845bf/ruff-0.14.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78d2b1097750d90ba82ce4ba676e85230a0ed694178ca5e61aa9b459970b3eb9", size = 13680909, upload-time = "2026-01-15T20:15:14.537Z" }, + { url = "https://files.pythonhosted.org/packages/61/61/cd37c9dd5bd0a3099ba79b2a5899ad417d8f3b04038810b0501a80814fd7/ruff-0.14.13-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7d0bf87705acbbcb8d4c24b2d77fbb73d40210a95c3903b443cd9e30824a5032", size = 15144215, upload-time = "2026-01-15T20:15:22.886Z" }, + { url = "https://files.pythonhosted.org/packages/56/8a/85502d7edbf98c2df7b8876f316c0157359165e16cdf98507c65c8d07d3d/ruff-0.14.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3eb5da8e2c9e9f13431032fdcbe7681de9ceda5835efee3269417c13f1fed5c", size = 14706067, upload-time = "2026-01-15T20:14:48.271Z" }, + { url = "https://files.pythonhosted.org/packages/7e/2f/de0df127feb2ee8c1e54354dc1179b4a23798f0866019528c938ba439aca/ruff-0.14.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:642442b42957093811cd8d2140dfadd19c7417030a7a68cf8d51fcdd5f217427", size = 14133916, upload-time = "2026-01-15T20:14:57.357Z" }, + { url = "https://files.pythonhosted.org/packages/0d/77/9b99686bb9fe07a757c82f6f95e555c7a47801a9305576a9c67e0a31d280/ruff-0.14.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4acdf009f32b46f6e8864af19cbf6841eaaed8638e65c8dac845aea0d703c841", size = 13859207, upload-time = "2026-01-15T20:14:55.111Z" }, + { url = "https://files.pythonhosted.org/packages/7d/46/2bdcb34a87a179a4d23022d818c1c236cb40e477faf0d7c9afb6813e5876/ruff-0.14.13-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:591a7f68860ea4e003917d19b5c4f5ac39ff558f162dc753a2c5de897fd5502c", size = 14043686, upload-time = "2026-01-15T20:14:52.841Z" }, + { url = "https://files.pythonhosted.org/packages/1a/a9/5c6a4f56a0512c691cf143371bcf60505ed0f0860f24a85da8bd123b2bf1/ruff-0.14.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:774c77e841cc6e046fc3e91623ce0903d1cd07e3a36b1a9fe79b81dab3de506b", size = 12663837, upload-time = "2026-01-15T20:15:18.921Z" }, + { url = "https://files.pythonhosted.org/packages/fe/bb/b920016ece7651fa7fcd335d9d199306665486694d4361547ccb19394c44/ruff-0.14.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:61f4e40077a1248436772bb6512db5fc4457fe4c49e7a94ea7c5088655dd21ae", size = 12805867, upload-time = "2026-01-15T20:14:59.272Z" }, + { url = "https://files.pythonhosted.org/packages/7d/b3/0bd909851e5696cd21e32a8fc25727e5f58f1934b3596975503e6e85415c/ruff-0.14.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6d02f1428357fae9e98ac7aa94b7e966fd24151088510d32cf6f902d6c09235e", size = 13208528, upload-time = "2026-01-15T20:15:03.732Z" }, + { url = "https://files.pythonhosted.org/packages/3b/3b/e2d94cb613f6bbd5155a75cbe072813756363eba46a3f2177a1fcd0cd670/ruff-0.14.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e399341472ce15237be0c0ae5fbceca4b04cd9bebab1a2b2c979e015455d8f0c", size = 13929242, upload-time = "2026-01-15T20:15:11.918Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c5/abd840d4132fd51a12f594934af5eba1d5d27298a6f5b5d6c3be45301caf/ruff-0.14.13-py3-none-win32.whl", hash = "sha256:ef720f529aec113968b45dfdb838ac8934e519711da53a0456038a0efecbd680", size = 12919024, upload-time = "2026-01-15T20:14:43.647Z" }, + { url = "https://files.pythonhosted.org/packages/c2/55/6384b0b8ce731b6e2ade2b5449bf07c0e4c31e8a2e68ea65b3bafadcecc5/ruff-0.14.13-py3-none-win_amd64.whl", hash = "sha256:6070bd026e409734b9257e03e3ef18c6e1a216f0435c6751d7a8ec69cb59abef", size = 14097887, upload-time = "2026-01-15T20:15:01.48Z" }, + { url = "https://files.pythonhosted.org/packages/4d/e1/7348090988095e4e39560cfc2f7555b1b2a7357deba19167b600fdf5215d/ruff-0.14.13-py3-none-win_arm64.whl", hash = "sha256:7ab819e14f1ad9fe39f246cfcc435880ef7a9390d81a2b6ac7e01039083dd247", size = 13080224, upload-time = "2026-01-15T20:14:45.853Z" }, ] [[package]] @@ -2066,55 +2067,55 @@ wheels = [ [[package]] name = "scipy" -version = "1.16.3" +version = "1.17.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0a/ca/d8ace4f98322d01abcd52d381134344bf7b431eba7ed8b42bdea5a3c2ac9/scipy-1.16.3.tar.gz", hash = "sha256:01e87659402762f43bd2fee13370553a17ada367d42e7487800bf2916535aecb", size = 30597883, upload-time = "2025-10-28T17:38:54.068Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/40/41/5bf55c3f386b1643812f3a5674edf74b26184378ef0f3e7c7a09a7e2ca7f/scipy-1.16.3-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:81fc5827606858cf71446a5e98715ba0e11f0dbc83d71c7409d05486592a45d6", size = 36659043, upload-time = "2025-10-28T17:32:40.285Z" }, - { url = "https://files.pythonhosted.org/packages/1e/0f/65582071948cfc45d43e9870bf7ca5f0e0684e165d7c9ef4e50d783073eb/scipy-1.16.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:c97176013d404c7346bf57874eaac5187d969293bf40497140b0a2b2b7482e07", size = 28898986, upload-time = "2025-10-28T17:32:45.325Z" }, - { url = "https://files.pythonhosted.org/packages/96/5e/36bf3f0ac298187d1ceadde9051177d6a4fe4d507e8f59067dc9dd39e650/scipy-1.16.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2b71d93c8a9936046866acebc915e2af2e292b883ed6e2cbe5c34beb094b82d9", size = 20889814, upload-time = "2025-10-28T17:32:49.277Z" }, - { url = "https://files.pythonhosted.org/packages/80/35/178d9d0c35394d5d5211bbff7ac4f2986c5488b59506fef9e1de13ea28d3/scipy-1.16.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3d4a07a8e785d80289dfe66b7c27d8634a773020742ec7187b85ccc4b0e7b686", size = 23565795, upload-time = "2025-10-28T17:32:53.337Z" }, - { url = "https://files.pythonhosted.org/packages/fa/46/d1146ff536d034d02f83c8afc3c4bab2eddb634624d6529a8512f3afc9da/scipy-1.16.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0553371015692a898e1aa858fed67a3576c34edefa6b7ebdb4e9dde49ce5c203", size = 33349476, upload-time = "2025-10-28T17:32:58.353Z" }, - { url = "https://files.pythonhosted.org/packages/79/2e/415119c9ab3e62249e18c2b082c07aff907a273741b3f8160414b0e9193c/scipy-1.16.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:72d1717fd3b5e6ec747327ce9bda32d5463f472c9dce9f54499e81fbd50245a1", size = 35676692, upload-time = "2025-10-28T17:33:03.88Z" }, - { url = "https://files.pythonhosted.org/packages/27/82/df26e44da78bf8d2aeaf7566082260cfa15955a5a6e96e6a29935b64132f/scipy-1.16.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fb2472e72e24d1530debe6ae078db70fb1605350c88a3d14bc401d6306dbffe", size = 36019345, upload-time = "2025-10-28T17:33:09.773Z" }, - { url = "https://files.pythonhosted.org/packages/82/31/006cbb4b648ba379a95c87262c2855cd0d09453e500937f78b30f02fa1cd/scipy-1.16.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c5192722cffe15f9329a3948c4b1db789fbb1f05c97899187dcf009b283aea70", size = 38678975, upload-time = "2025-10-28T17:33:15.809Z" }, - { url = "https://files.pythonhosted.org/packages/c2/7f/acbd28c97e990b421af7d6d6cd416358c9c293fc958b8529e0bd5d2a2a19/scipy-1.16.3-cp312-cp312-win_amd64.whl", hash = "sha256:56edc65510d1331dae01ef9b658d428e33ed48b4f77b1d51caf479a0253f96dc", size = 38555926, upload-time = "2025-10-28T17:33:21.388Z" }, - { url = "https://files.pythonhosted.org/packages/ce/69/c5c7807fd007dad4f48e0a5f2153038dc96e8725d3345b9ee31b2b7bed46/scipy-1.16.3-cp312-cp312-win_arm64.whl", hash = "sha256:a8a26c78ef223d3e30920ef759e25625a0ecdd0d60e5a8818b7513c3e5384cf2", size = 25463014, upload-time = "2025-10-28T17:33:25.975Z" }, - { url = "https://files.pythonhosted.org/packages/72/f1/57e8327ab1508272029e27eeef34f2302ffc156b69e7e233e906c2a5c379/scipy-1.16.3-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:d2ec56337675e61b312179a1ad124f5f570c00f920cc75e1000025451b88241c", size = 36617856, upload-time = "2025-10-28T17:33:31.375Z" }, - { url = "https://files.pythonhosted.org/packages/44/13/7e63cfba8a7452eb756306aa2fd9b37a29a323b672b964b4fdeded9a3f21/scipy-1.16.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:16b8bc35a4cc24db80a0ec836a9286d0e31b2503cb2fd7ff7fb0e0374a97081d", size = 28874306, upload-time = "2025-10-28T17:33:36.516Z" }, - { url = "https://files.pythonhosted.org/packages/15/65/3a9400efd0228a176e6ec3454b1fa998fbbb5a8defa1672c3f65706987db/scipy-1.16.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:5803c5fadd29de0cf27fa08ccbfe7a9e5d741bf63e4ab1085437266f12460ff9", size = 20865371, upload-time = "2025-10-28T17:33:42.094Z" }, - { url = "https://files.pythonhosted.org/packages/33/d7/eda09adf009a9fb81827194d4dd02d2e4bc752cef16737cc4ef065234031/scipy-1.16.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:b81c27fc41954319a943d43b20e07c40bdcd3ff7cf013f4fb86286faefe546c4", size = 23524877, upload-time = "2025-10-28T17:33:48.483Z" }, - { url = "https://files.pythonhosted.org/packages/7d/6b/3f911e1ebc364cb81320223a3422aab7d26c9c7973109a9cd0f27c64c6c0/scipy-1.16.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0c3b4dd3d9b08dbce0f3440032c52e9e2ab9f96ade2d3943313dfe51a7056959", size = 33342103, upload-time = "2025-10-28T17:33:56.495Z" }, - { url = "https://files.pythonhosted.org/packages/21/f6/4bfb5695d8941e5c570a04d9fcd0d36bce7511b7d78e6e75c8f9791f82d0/scipy-1.16.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7dc1360c06535ea6116a2220f760ae572db9f661aba2d88074fe30ec2aa1ff88", size = 35697297, upload-time = "2025-10-28T17:34:04.722Z" }, - { url = "https://files.pythonhosted.org/packages/04/e1/6496dadbc80d8d896ff72511ecfe2316b50313bfc3ebf07a3f580f08bd8c/scipy-1.16.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:663b8d66a8748051c3ee9c96465fb417509315b99c71550fda2591d7dd634234", size = 36021756, upload-time = "2025-10-28T17:34:13.482Z" }, - { url = "https://files.pythonhosted.org/packages/fe/bd/a8c7799e0136b987bda3e1b23d155bcb31aec68a4a472554df5f0937eef7/scipy-1.16.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eab43fae33a0c39006a88096cd7b4f4ef545ea0447d250d5ac18202d40b6611d", size = 38696566, upload-time = "2025-10-28T17:34:22.384Z" }, - { url = "https://files.pythonhosted.org/packages/cd/01/1204382461fcbfeb05b6161b594f4007e78b6eba9b375382f79153172b4d/scipy-1.16.3-cp313-cp313-win_amd64.whl", hash = "sha256:062246acacbe9f8210de8e751b16fc37458213f124bef161a5a02c7a39284304", size = 38529877, upload-time = "2025-10-28T17:35:51.076Z" }, - { url = "https://files.pythonhosted.org/packages/7f/14/9d9fbcaa1260a94f4bb5b64ba9213ceb5d03cd88841fe9fd1ffd47a45b73/scipy-1.16.3-cp313-cp313-win_arm64.whl", hash = "sha256:50a3dbf286dbc7d84f176f9a1574c705f277cb6565069f88f60db9eafdbe3ee2", size = 25455366, upload-time = "2025-10-28T17:35:59.014Z" }, - { url = "https://files.pythonhosted.org/packages/e2/a3/9ec205bd49f42d45d77f1730dbad9ccf146244c1647605cf834b3a8c4f36/scipy-1.16.3-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:fb4b29f4cf8cc5a8d628bc8d8e26d12d7278cd1f219f22698a378c3d67db5e4b", size = 37027931, upload-time = "2025-10-28T17:34:31.451Z" }, - { url = "https://files.pythonhosted.org/packages/25/06/ca9fd1f3a4589cbd825b1447e5db3a8ebb969c1eaf22c8579bd286f51b6d/scipy-1.16.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:8d09d72dc92742988b0e7750bddb8060b0c7079606c0d24a8cc8e9c9c11f9079", size = 29400081, upload-time = "2025-10-28T17:34:39.087Z" }, - { url = "https://files.pythonhosted.org/packages/6a/56/933e68210d92657d93fb0e381683bc0e53a965048d7358ff5fbf9e6a1b17/scipy-1.16.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:03192a35e661470197556de24e7cb1330d84b35b94ead65c46ad6f16f6b28f2a", size = 21391244, upload-time = "2025-10-28T17:34:45.234Z" }, - { url = "https://files.pythonhosted.org/packages/a8/7e/779845db03dc1418e215726329674b40576879b91814568757ff0014ad65/scipy-1.16.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:57d01cb6f85e34f0946b33caa66e892aae072b64b034183f3d87c4025802a119", size = 23929753, upload-time = "2025-10-28T17:34:51.793Z" }, - { url = "https://files.pythonhosted.org/packages/4c/4b/f756cf8161d5365dcdef9e5f460ab226c068211030a175d2fc7f3f41ca64/scipy-1.16.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:96491a6a54e995f00a28a3c3badfff58fd093bf26cd5fb34a2188c8c756a3a2c", size = 33496912, upload-time = "2025-10-28T17:34:59.8Z" }, - { url = "https://files.pythonhosted.org/packages/09/b5/222b1e49a58668f23839ca1542a6322bb095ab8d6590d4f71723869a6c2c/scipy-1.16.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cd13e354df9938598af2be05822c323e97132d5e6306b83a3b4ee6724c6e522e", size = 35802371, upload-time = "2025-10-28T17:35:08.173Z" }, - { url = "https://files.pythonhosted.org/packages/c1/8d/5964ef68bb31829bde27611f8c9deeac13764589fe74a75390242b64ca44/scipy-1.16.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:63d3cdacb8a824a295191a723ee5e4ea7768ca5ca5f2838532d9f2e2b3ce2135", size = 36190477, upload-time = "2025-10-28T17:35:16.7Z" }, - { url = "https://files.pythonhosted.org/packages/ab/f2/b31d75cb9b5fa4dd39a0a931ee9b33e7f6f36f23be5ef560bf72e0f92f32/scipy-1.16.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e7efa2681ea410b10dde31a52b18b0154d66f2485328830e45fdf183af5aefc6", size = 38796678, upload-time = "2025-10-28T17:35:26.354Z" }, - { url = "https://files.pythonhosted.org/packages/b4/1e/b3723d8ff64ab548c38d87055483714fefe6ee20e0189b62352b5e015bb1/scipy-1.16.3-cp313-cp313t-win_amd64.whl", hash = "sha256:2d1ae2cf0c350e7705168ff2429962a89ad90c2d49d1dd300686d8b2a5af22fc", size = 38640178, upload-time = "2025-10-28T17:35:35.304Z" }, - { url = "https://files.pythonhosted.org/packages/8e/f3/d854ff38789aca9b0cc23008d607ced9de4f7ab14fa1ca4329f86b3758ca/scipy-1.16.3-cp313-cp313t-win_arm64.whl", hash = "sha256:0c623a54f7b79dd88ef56da19bc2873afec9673a48f3b85b18e4d402bdd29a5a", size = 25803246, upload-time = "2025-10-28T17:35:42.155Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/56/3e/9cca699f3486ce6bc12ff46dc2031f1ec8eb9ccc9a320fdaf925f1417426/scipy-1.17.0.tar.gz", hash = "sha256:2591060c8e648d8b96439e111ac41fd8342fdeff1876be2e19dea3fe8930454e", size = 30396830, upload-time = "2026-01-10T21:34:23.009Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/11/7241a63e73ba5a516f1930ac8d5b44cbbfabd35ac73a2d08ca206df007c4/scipy-1.17.0-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:0d5018a57c24cb1dd828bcf51d7b10e65986d549f52ef5adb6b4d1ded3e32a57", size = 31364580, upload-time = "2026-01-10T21:25:25.717Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1d/5057f812d4f6adc91a20a2d6f2ebcdb517fdbc87ae3acc5633c9b97c8ba5/scipy-1.17.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:88c22af9e5d5a4f9e027e26772cc7b5922fab8bcc839edb3ae33de404feebd9e", size = 27969012, upload-time = "2026-01-10T21:25:30.921Z" }, + { url = "https://files.pythonhosted.org/packages/e3/21/f6ec556c1e3b6ec4e088da667d9987bb77cc3ab3026511f427dc8451187d/scipy-1.17.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f3cd947f20fe17013d401b64e857c6b2da83cae567adbb75b9dcba865abc66d8", size = 20140691, upload-time = "2026-01-10T21:25:34.802Z" }, + { url = "https://files.pythonhosted.org/packages/7a/fe/5e5ad04784964ba964a96f16c8d4676aa1b51357199014dce58ab7ec5670/scipy-1.17.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e8c0b331c2c1f531eb51f1b4fc9ba709521a712cce58f1aa627bc007421a5306", size = 22463015, upload-time = "2026-01-10T21:25:39.277Z" }, + { url = "https://files.pythonhosted.org/packages/4a/69/7c347e857224fcaf32a34a05183b9d8a7aca25f8f2d10b8a698b8388561a/scipy-1.17.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5194c445d0a1c7a6c1a4a4681b6b7c71baad98ff66d96b949097e7513c9d6742", size = 32724197, upload-time = "2026-01-10T21:25:44.084Z" }, + { url = "https://files.pythonhosted.org/packages/d1/fe/66d73b76d378ba8cc2fe605920c0c75092e3a65ae746e1e767d9d020a75a/scipy-1.17.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9eeb9b5f5997f75507814ed9d298ab23f62cf79f5a3ef90031b1ee2506abdb5b", size = 35009148, upload-time = "2026-01-10T21:25:50.591Z" }, + { url = "https://files.pythonhosted.org/packages/af/07/07dec27d9dc41c18d8c43c69e9e413431d20c53a0339c388bcf72f353c4b/scipy-1.17.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:40052543f7bbe921df4408f46003d6f01c6af109b9e2c8a66dd1cf6cf57f7d5d", size = 34798766, upload-time = "2026-01-10T21:25:59.41Z" }, + { url = "https://files.pythonhosted.org/packages/81/61/0470810c8a093cdacd4ba7504b8a218fd49ca070d79eca23a615f5d9a0b0/scipy-1.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0cf46c8013fec9d3694dc572f0b54100c28405d55d3e2cb15e2895b25057996e", size = 37405953, upload-time = "2026-01-10T21:26:07.75Z" }, + { url = "https://files.pythonhosted.org/packages/92/ce/672ed546f96d5d41ae78c4b9b02006cedd0b3d6f2bf5bb76ea455c320c28/scipy-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:0937a0b0d8d593a198cededd4c439a0ea216a3f36653901ea1f3e4be949056f8", size = 36328121, upload-time = "2026-01-10T21:26:16.509Z" }, + { url = "https://files.pythonhosted.org/packages/9d/21/38165845392cae67b61843a52c6455d47d0cc2a40dd495c89f4362944654/scipy-1.17.0-cp312-cp312-win_arm64.whl", hash = "sha256:f603d8a5518c7426414d1d8f82e253e454471de682ce5e39c29adb0df1efb86b", size = 24314368, upload-time = "2026-01-10T21:26:23.087Z" }, + { url = "https://files.pythonhosted.org/packages/0c/51/3468fdfd49387ddefee1636f5cf6d03ce603b75205bf439bbf0e62069bfd/scipy-1.17.0-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:65ec32f3d32dfc48c72df4291345dae4f048749bc8d5203ee0a3f347f96c5ce6", size = 31344101, upload-time = "2026-01-10T21:26:30.25Z" }, + { url = "https://files.pythonhosted.org/packages/b2/9a/9406aec58268d437636069419e6977af953d1e246df941d42d3720b7277b/scipy-1.17.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:1f9586a58039d7229ce77b52f8472c972448cded5736eaf102d5658bbac4c269", size = 27950385, upload-time = "2026-01-10T21:26:36.801Z" }, + { url = "https://files.pythonhosted.org/packages/4f/98/e7342709e17afdfd1b26b56ae499ef4939b45a23a00e471dfb5375eea205/scipy-1.17.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:9fad7d3578c877d606b1150135c2639e9de9cecd3705caa37b66862977cc3e72", size = 20122115, upload-time = "2026-01-10T21:26:42.107Z" }, + { url = "https://files.pythonhosted.org/packages/fd/0e/9eeeb5357a64fd157cbe0302c213517c541cc16b8486d82de251f3c68ede/scipy-1.17.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:423ca1f6584fc03936972b5f7c06961670dbba9f234e71676a7c7ccf938a0d61", size = 22442402, upload-time = "2026-01-10T21:26:48.029Z" }, + { url = "https://files.pythonhosted.org/packages/c9/10/be13397a0e434f98e0c79552b2b584ae5bb1c8b2be95db421533bbca5369/scipy-1.17.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe508b5690e9eaaa9467fc047f833af58f1152ae51a0d0aed67aa5801f4dd7d6", size = 32696338, upload-time = "2026-01-10T21:26:55.521Z" }, + { url = "https://files.pythonhosted.org/packages/63/1e/12fbf2a3bb240161651c94bb5cdd0eae5d4e8cc6eaeceb74ab07b12a753d/scipy-1.17.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6680f2dfd4f6182e7d6db161344537da644d1cf85cf293f015c60a17ecf08752", size = 34977201, upload-time = "2026-01-10T21:27:03.501Z" }, + { url = "https://files.pythonhosted.org/packages/19/5b/1a63923e23ccd20bd32156d7dd708af5bbde410daa993aa2500c847ab2d2/scipy-1.17.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eec3842ec9ac9de5917899b277428886042a93db0b227ebbe3a333b64ec7643d", size = 34777384, upload-time = "2026-01-10T21:27:11.423Z" }, + { url = "https://files.pythonhosted.org/packages/39/22/b5da95d74edcf81e540e467202a988c50fef41bd2011f46e05f72ba07df6/scipy-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d7425fcafbc09a03731e1bc05581f5fad988e48c6a861f441b7ab729a49a55ea", size = 37379586, upload-time = "2026-01-10T21:27:20.171Z" }, + { url = "https://files.pythonhosted.org/packages/b9/b6/8ac583d6da79e7b9e520579f03007cb006f063642afd6b2eeb16b890bf93/scipy-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:87b411e42b425b84777718cc41516b8a7e0795abfa8e8e1d573bf0ef014f0812", size = 36287211, upload-time = "2026-01-10T21:28:43.122Z" }, + { url = "https://files.pythonhosted.org/packages/55/fb/7db19e0b3e52f882b420417644ec81dd57eeef1bd1705b6f689d8ff93541/scipy-1.17.0-cp313-cp313-win_arm64.whl", hash = "sha256:357ca001c6e37601066092e7c89cca2f1ce74e2a520ca78d063a6d2201101df2", size = 24312646, upload-time = "2026-01-10T21:28:49.893Z" }, + { url = "https://files.pythonhosted.org/packages/20/b6/7feaa252c21cc7aff335c6c55e1b90ab3e3306da3f048109b8b639b94648/scipy-1.17.0-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:ec0827aa4d36cb79ff1b81de898e948a51ac0b9b1c43e4a372c0508c38c0f9a3", size = 31693194, upload-time = "2026-01-10T21:27:27.454Z" }, + { url = "https://files.pythonhosted.org/packages/76/bb/bbb392005abce039fb7e672cb78ac7d158700e826b0515cab6b5b60c26fb/scipy-1.17.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:819fc26862b4b3c73a60d486dbb919202f3d6d98c87cf20c223511429f2d1a97", size = 28365415, upload-time = "2026-01-10T21:27:34.26Z" }, + { url = "https://files.pythonhosted.org/packages/37/da/9d33196ecc99fba16a409c691ed464a3a283ac454a34a13a3a57c0d66f3a/scipy-1.17.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:363ad4ae2853d88ebcde3ae6ec46ccca903ea9835ee8ba543f12f575e7b07e4e", size = 20537232, upload-time = "2026-01-10T21:27:40.306Z" }, + { url = "https://files.pythonhosted.org/packages/56/9d/f4b184f6ddb28e9a5caea36a6f98e8ecd2a524f9127354087ce780885d83/scipy-1.17.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:979c3a0ff8e5ba254d45d59ebd38cde48fce4f10b5125c680c7a4bfe177aab07", size = 22791051, upload-time = "2026-01-10T21:27:46.539Z" }, + { url = "https://files.pythonhosted.org/packages/9b/9d/025cccdd738a72140efc582b1641d0dd4caf2e86c3fb127568dc80444e6e/scipy-1.17.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:130d12926ae34399d157de777472bf82e9061c60cc081372b3118edacafe1d00", size = 32815098, upload-time = "2026-01-10T21:27:54.389Z" }, + { url = "https://files.pythonhosted.org/packages/48/5f/09b879619f8bca15ce392bfc1894bd9c54377e01d1b3f2f3b595a1b4d945/scipy-1.17.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e886000eb4919eae3a44f035e63f0fd8b651234117e8f6f29bad1cd26e7bc45", size = 35031342, upload-time = "2026-01-10T21:28:03.012Z" }, + { url = "https://files.pythonhosted.org/packages/f2/9a/f0f0a9f0aa079d2f106555b984ff0fbb11a837df280f04f71f056ea9c6e4/scipy-1.17.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:13c4096ac6bc31d706018f06a49abe0485f96499deb82066b94d19b02f664209", size = 34893199, upload-time = "2026-01-10T21:28:10.832Z" }, + { url = "https://files.pythonhosted.org/packages/90/b8/4f0f5cf0c5ea4d7548424e6533e6b17d164f34a6e2fb2e43ffebb6697b06/scipy-1.17.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cacbaddd91fcffde703934897c5cd2c7cb0371fac195d383f4e1f1c5d3f3bd04", size = 37438061, upload-time = "2026-01-10T21:28:19.684Z" }, + { url = "https://files.pythonhosted.org/packages/f9/cc/2bd59140ed3b2fa2882fb15da0a9cb1b5a6443d67cfd0d98d4cec83a57ec/scipy-1.17.0-cp313-cp313t-win_amd64.whl", hash = "sha256:edce1a1cf66298cccdc48a1bdf8fb10a3bf58e8b58d6c3883dd1530e103f87c0", size = 36328593, upload-time = "2026-01-10T21:28:28.007Z" }, + { url = "https://files.pythonhosted.org/packages/13/1b/c87cc44a0d2c7aaf0f003aef2904c3d097b422a96c7e7c07f5efd9073c1b/scipy-1.17.0-cp313-cp313t-win_arm64.whl", hash = "sha256:30509da9dbec1c2ed8f168b8d8aa853bc6723fede1dbc23c7d43a56f5ab72a67", size = 24625083, upload-time = "2026-01-10T21:28:35.188Z" }, ] [[package]] name = "scipy-stubs" -version = "1.17.0.0" +version = "1.17.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "optype", extra = ["numpy"] }, ] -sdist = { url = "https://files.pythonhosted.org/packages/12/4c/ae02bab2da86641edb086e1ac72281b208282a4dc0a9713fba4b823d22d9/scipy_stubs-1.17.0.0.tar.gz", hash = "sha256:01953f1c7967876be942afa21c1cc864c92714d01b1970ef8bdd468b4468d4b7", size = 367603, upload-time = "2025-12-31T18:58:25.911Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/a2/7f52edf1185ffcbf26cae1adede995f923c60a3a1f366bd1cb4cbae41817/scipy_stubs-1.17.0.1.tar.gz", hash = "sha256:029ef77b3984be53a914ac90af3b78c5543af7275eb126c8cec09e7bc72f623c", size = 372323, upload-time = "2026-01-14T16:34:52.838Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/53/bc0ad50243638982c572894c9dd5205e690d34fda7ec6cadf1c1ce157e14/scipy_stubs-1.17.0.0-py3-none-any.whl", hash = "sha256:17b336fa6c56afb0cf47e426e3cb36a7eb8b73351ee0a52d1e5cef7634b5e936", size = 572744, upload-time = "2025-12-31T18:58:24.297Z" }, + { url = "https://files.pythonhosted.org/packages/df/7f/6b99d2f0b75738487e3127dc8fbfff04214cd29900118f5a1f945c34271f/scipy_stubs-1.17.0.1-py3-none-any.whl", hash = "sha256:235bdebce396a9bb48236525aedf04a6efa66dcca8b46105549f35f1f5c4cbb7", size = 577357, upload-time = "2026-01-14T16:34:50.907Z" }, ] [[package]] @@ -2392,44 +2393,44 @@ wheels = [ [[package]] name = "trimesh" -version = "4.10.1" +version = "4.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/83/69/eedfeb084460d429368e03db83ed41b18d6de4fd4945de7eb8874b9fae36/trimesh-4.10.1.tar.gz", hash = "sha256:2067ebb8dcde0d7f00c2a85bfcae4aa891c40898e5f14232592429025ee2c593", size = 831998, upload-time = "2025-12-07T00:39:05.838Z" } +sdist = { url = "https://files.pythonhosted.org/packages/da/47/f618103c076b06ac79f1ba71e26ca7df0292c3e2b83535d4873e7f127f95/trimesh-4.11.0.tar.gz", hash = "sha256:0b4acdcf28f21013385ccf81619a9dce703af348f69b198180a2212b1bc67821", size = 834847, upload-time = "2026-01-07T19:51:53.043Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d5/0c/f08f0d16b4f97ec2ea6d542b9a70472a344384382fa3543a12ec417cc063/trimesh-4.10.1-py3-none-any.whl", hash = "sha256:4e81fae696683dfe912ef54ce124869487d35d267b87e10fe07fc05ab62aaadb", size = 737037, upload-time = "2025-12-07T00:39:04.086Z" }, + { url = "https://files.pythonhosted.org/packages/23/cc/5056718bf473be51712ecf74800093a13d69c576f1ca0e0f3cf4684567cf/trimesh-4.11.0-py3-none-any.whl", hash = "sha256:6237019fed3bdc8d68acc45a47a4ea62db1483c3fab5c87d587a48a0e69bbff3", size = 740342, upload-time = "2026-01-07T19:51:50.993Z" }, ] [[package]] name = "ty" -version = "0.0.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/7b/4f677c622d58563c593c32081f8a8572afd90e43dc15b0dedd27b4305038/ty-0.0.9.tar.gz", hash = "sha256:83f980c46df17586953ab3060542915827b43c4748a59eea04190c59162957fe", size = 4858642, upload-time = "2026-01-05T12:24:56.528Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/3f/c1ee119738b401a8081ff84341781122296b66982e5982e6f162d946a1ff/ty-0.0.9-py3-none-linux_armv6l.whl", hash = "sha256:dd270d4dd6ebeb0abb37aee96cbf9618610723677f500fec1ba58f35bfa8337d", size = 9763596, upload-time = "2026-01-05T12:24:37.43Z" }, - { url = "https://files.pythonhosted.org/packages/63/41/6b0669ef4cd806d4bd5c30263e6b732a362278abac1bc3a363a316cde896/ty-0.0.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:debfb2ba418b00e86ffd5403cb666b3f04e16853f070439517dd1eaaeeff9255", size = 9591514, upload-time = "2026-01-05T12:24:26.891Z" }, - { url = "https://files.pythonhosted.org/packages/02/a1/874aa756aee5118e690340a771fb9ded0d0c2168c0b7cc7d9561c2a750b0/ty-0.0.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:107c76ebb05a13cdb669172956421f7ffd289ad98f36d42a44a465588d434d58", size = 9097773, upload-time = "2026-01-05T12:24:14.442Z" }, - { url = "https://files.pythonhosted.org/packages/32/62/cb9a460cf03baab77b3361d13106b93b40c98e274d07c55f333ce3c716f6/ty-0.0.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6868ca5c87ca0caa1b3cb84603c767356242b0659b88307eda69b2fb0bfa416b", size = 9581824, upload-time = "2026-01-05T12:24:35.074Z" }, - { url = "https://files.pythonhosted.org/packages/5a/97/633ecb348c75c954f09f8913669de8c440b13b43ea7d214503f3f1c4bb60/ty-0.0.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d14a4aa0eb5c1d3591c2adbdda4e44429a6bb5d2e298a704398bb2a7ccdafdfe", size = 9591050, upload-time = "2026-01-05T12:24:08.804Z" }, - { url = "https://files.pythonhosted.org/packages/6f/e6/4b0c6a7a8a234e2113f88c80cc7aaa9af5868de7a693859f3c49da981934/ty-0.0.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01bd4466504cefa36b465c6608e9af4504415fa67f6affc01c7d6ce36663c7f4", size = 10018262, upload-time = "2026-01-05T12:24:53.791Z" }, - { url = "https://files.pythonhosted.org/packages/cb/97/076d72a028f6b31e0b87287aa27c5b71a2f9927ee525260ea9f2f56828b8/ty-0.0.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:76c8253d1b30bc2c3eaa1b1411a1c34423decde0f4de0277aa6a5ceacfea93d9", size = 10911642, upload-time = "2026-01-05T12:24:48.264Z" }, - { url = "https://files.pythonhosted.org/packages/3f/5a/705d6a5ed07ea36b1f23592c3f0dbc8fc7649267bfbb3bf06464cdc9a98a/ty-0.0.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8992fa4a9c6a5434eae4159fdd4842ec8726259bfd860e143ab95d078de6f8e3", size = 10632468, upload-time = "2026-01-05T12:24:24.118Z" }, - { url = "https://files.pythonhosted.org/packages/44/78/4339a254537488d62bf392a936b3ec047702c0cc33d6ce3a5d613f275cd0/ty-0.0.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c79d503d151acb4a145a3d98702d07cb641c47292f63e5ffa0151e4020a5d33", size = 10273422, upload-time = "2026-01-05T12:24:45.8Z" }, - { url = "https://files.pythonhosted.org/packages/90/40/e7f386e87c9abd3670dcee8311674d7e551baa23b2e4754e2405976e6c92/ty-0.0.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7a7ebf89ed276b564baa1f0dd9cd708e7b5aa89f19ce1b2f7d7132075abf93e", size = 10120289, upload-time = "2026-01-05T12:24:17.424Z" }, - { url = "https://files.pythonhosted.org/packages/f7/46/1027442596e725c50d0d1ab5179e9fa78a398ab412994b3006d0ee0899c7/ty-0.0.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ae3866e50109d2400a886bb11d9ef607f23afc020b226af773615cf82ae61141", size = 9566657, upload-time = "2026-01-05T12:24:51.048Z" }, - { url = "https://files.pythonhosted.org/packages/56/be/df921cf1967226aa01690152002b370a7135c6cced81e86c12b86552cdc4/ty-0.0.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:185244a5eacfcd8f5e2d85b95e4276316772f1e586520a6cb24aa072ec1bac26", size = 9610334, upload-time = "2026-01-05T12:24:20.334Z" }, - { url = "https://files.pythonhosted.org/packages/ac/e8/f085268860232cc92ebe95415e5c8640f7f1797ac3a49ddd137c6222924d/ty-0.0.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f834ff27d940edb24b2e86bbb3fb45ab9e07cf59ca8c5ac615095b2542786408", size = 9726701, upload-time = "2026-01-05T12:24:29.785Z" }, - { url = "https://files.pythonhosted.org/packages/42/b4/9394210c66041cd221442e38f68a596945103d9446ece505889ffa9b3da9/ty-0.0.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:773f4b3ba046de952d7c1ad3a2c09b24f3ed4bc8342ae3cbff62ebc14aa6d48c", size = 10227082, upload-time = "2026-01-05T12:24:40.132Z" }, - { url = "https://files.pythonhosted.org/packages/dc/9f/75951eb573b473d35dd9570546fc1319f7ca2d5b5c50a5825ba6ea6cb33a/ty-0.0.9-py3-none-win32.whl", hash = "sha256:1f20f67e373038ff20f36d5449e787c0430a072b92d5933c5b6e6fc79d3de4c8", size = 9176458, upload-time = "2026-01-05T12:24:32.559Z" }, - { url = "https://files.pythonhosted.org/packages/9b/80/b1cdf71ac874e72678161e25e2326a7d30bc3489cd3699561355a168e54f/ty-0.0.9-py3-none-win_amd64.whl", hash = "sha256:2c415f3bbb730f8de2e6e0b3c42eb3a91f1b5fbbcaaead2e113056c3b361c53c", size = 10040479, upload-time = "2026-01-05T12:24:42.697Z" }, - { url = "https://files.pythonhosted.org/packages/b5/8f/abc75c4bb774b12698629f02d0d12501b0a7dff9c31dc3bd6b6c6467e90a/ty-0.0.9-py3-none-win_arm64.whl", hash = "sha256:48e339d794542afeed710ea4f846ead865cc38cecc335a9c781804d02eaa2722", size = 9543127, upload-time = "2026-01-05T12:24:11.731Z" }, +version = "0.0.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/78/ba1a4ad403c748fbba8be63b7e774a90e80b67192f6443d624c64fe4aaab/ty-0.0.12.tar.gz", hash = "sha256:cd01810e106c3b652a01b8f784dd21741de9fdc47bd595d02c122a7d5cefeee7", size = 4981303, upload-time = "2026-01-14T22:30:48.537Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/8f/c21314d074dda5fb13d3300fa6733fd0d8ff23ea83a721818740665b6314/ty-0.0.12-py3-none-linux_armv6l.whl", hash = "sha256:eb9da1e2c68bd754e090eab39ed65edf95168d36cbeb43ff2bd9f86b4edd56d1", size = 9614164, upload-time = "2026-01-14T22:30:44.016Z" }, + { url = "https://files.pythonhosted.org/packages/09/28/f8a4d944d13519d70c486e8f96d6fa95647ac2aa94432e97d5cfec1f42f6/ty-0.0.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c181f42aa19b0ed7f1b0c2d559980b1f1d77cc09419f51c8321c7ddf67758853", size = 9542337, upload-time = "2026-01-14T22:30:05.687Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9c/f576e360441de7a8201daa6dc4ebc362853bc5305e059cceeb02ebdd9a48/ty-0.0.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1f829e1eecd39c3e1b032149db7ae6a3284f72fc36b42436e65243a9ed1173db", size = 8909582, upload-time = "2026-01-14T22:30:46.089Z" }, + { url = "https://files.pythonhosted.org/packages/d6/13/0898e494032a5d8af3060733d12929e3e7716db6c75eac63fa125730a3e7/ty-0.0.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f45162e7826e1789cf3374627883cdeb0d56b82473a0771923e4572928e90be3", size = 9384932, upload-time = "2026-01-14T22:30:13.769Z" }, + { url = "https://files.pythonhosted.org/packages/e4/1a/b35b6c697008a11d4cedfd34d9672db2f0a0621ec80ece109e13fca4dfef/ty-0.0.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d11fec40b269bec01e751b2337d1c7ffa959a2c2090a950d7e21c2792442cccd", size = 9453140, upload-time = "2026-01-14T22:30:11.131Z" }, + { url = "https://files.pythonhosted.org/packages/dd/1e/71c9edbc79a3c88a0711324458f29c7dbf6c23452c6e760dc25725483064/ty-0.0.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09d99e37e761a4d2651ad9d5a610d11235fbcbf35dc6d4bc04abf54e7cf894f1", size = 9960680, upload-time = "2026-01-14T22:30:33.621Z" }, + { url = "https://files.pythonhosted.org/packages/0e/75/39375129f62dd22f6ad5a99cd2a42fd27d8b91b235ce2db86875cdad397d/ty-0.0.12-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d9ca0cdb17bd37397da7b16a7cd23423fc65c3f9691e453ad46c723d121225a1", size = 10904518, upload-time = "2026-01-14T22:30:08.464Z" }, + { url = "https://files.pythonhosted.org/packages/32/5e/26c6d88fafa11a9d31ca9f4d12989f57782ec61e7291d4802d685b5be118/ty-0.0.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcf2757b905e7eddb7e456140066335b18eb68b634a9f72d6f54a427ab042c64", size = 10525001, upload-time = "2026-01-14T22:30:16.454Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a5/2f0b91894af13187110f9ad7ee926d86e4e6efa755c9c88a820ed7f84c85/ty-0.0.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:00cf34c1ebe1147efeda3021a1064baa222c18cdac114b7b050bbe42deb4ca80", size = 10307103, upload-time = "2026-01-14T22:30:41.221Z" }, + { url = "https://files.pythonhosted.org/packages/4b/77/13d0410827e4bc713ebb7fdaf6b3590b37dcb1b82e0a81717b65548f2442/ty-0.0.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb3a655bd869352e9a22938d707631ac9fbca1016242b1f6d132d78f347c851", size = 10072737, upload-time = "2026-01-14T22:30:51.783Z" }, + { url = "https://files.pythonhosted.org/packages/e1/dd/fc36d8bac806c74cf04b4ca735bca14d19967ca84d88f31e121767880df1/ty-0.0.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4658e282c7cb82be304052f8f64f9925f23c3c4f90eeeb32663c74c4b095d7ba", size = 9368726, upload-time = "2026-01-14T22:30:18.683Z" }, + { url = "https://files.pythonhosted.org/packages/54/70/9e8e461647550f83e2fe54bc632ccbdc17a4909644783cdbdd17f7296059/ty-0.0.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:c167d838eaaa06e03bb66a517f75296b643d950fbd93c1d1686a187e5a8dbd1f", size = 9454704, upload-time = "2026-01-14T22:30:22.759Z" }, + { url = "https://files.pythonhosted.org/packages/04/9b/6292cf7c14a0efeca0539cf7d78f453beff0475cb039fbea0eb5d07d343d/ty-0.0.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2956e0c9ab7023533b461d8a0e6b2ea7b78e01a8dde0688e8234d0fce10c4c1c", size = 9649829, upload-time = "2026-01-14T22:30:31.234Z" }, + { url = "https://files.pythonhosted.org/packages/49/bd/472a5d2013371e4870886cff791c94abdf0b92d43d305dd0f8e06b6ff719/ty-0.0.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5c6a3fd7479580009f21002f3828320621d8a82d53b7ba36993234e3ccad58c8", size = 10162814, upload-time = "2026-01-14T22:30:36.174Z" }, + { url = "https://files.pythonhosted.org/packages/31/e9/2ecbe56826759845a7c21d80aa28187865ea62bc9757b056f6cbc06f78ed/ty-0.0.12-py3-none-win32.whl", hash = "sha256:a91c24fd75c0f1796d8ede9083e2c0ec96f106dbda73a09fe3135e075d31f742", size = 9140115, upload-time = "2026-01-14T22:30:38.903Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6d/d9531eff35a5c0ec9dbc10231fac21f9dd6504814048e81d6ce1c84dc566/ty-0.0.12-py3-none-win_amd64.whl", hash = "sha256:df151894be55c22d47068b0f3b484aff9e638761e2267e115d515fcc9c5b4a4b", size = 9884532, upload-time = "2026-01-14T22:30:25.112Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f3/20b49e75967023b123a221134548ad7000f9429f13fdcdda115b4c26305f/ty-0.0.12-py3-none-win_arm64.whl", hash = "sha256:cea99d334b05629de937ce52f43278acf155d3a316ad6a35356635f886be20ea", size = 9313974, upload-time = "2026-01-14T22:30:27.44Z" }, ] [[package]] name = "typer" -version = "0.21.0" +version = "0.21.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -2437,9 +2438,9 @@ dependencies = [ { name = "shellingham" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/85/30/ff9ede605e3bd086b4dd842499814e128500621f7951ca1e5ce84bbf61b1/typer-0.21.0.tar.gz", hash = "sha256:c87c0d2b6eee3b49c5c64649ec92425492c14488096dfbc8a0c2799b2f6f9c53", size = 106781, upload-time = "2025-12-25T09:54:53.651Z" } +sdist = { url = "https://files.pythonhosted.org/packages/36/bf/8825b5929afd84d0dabd606c67cd57b8388cb3ec385f7ef19c5cc2202069/typer-0.21.1.tar.gz", hash = "sha256:ea835607cd752343b6b2b7ce676893e5a0324082268b48f27aa058bdb7d2145d", size = 110371, upload-time = "2026-01-06T11:21:10.989Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/e4/5ebc1899d31d2b1601b32d21cfb4bba022ae6fce323d365f0448031b1660/typer-0.21.0-py3-none-any.whl", hash = "sha256:c79c01ca6b30af9fd48284058a7056ba0d3bf5cf10d0ff3d0c5b11b68c258ac6", size = 47109, upload-time = "2025-12-25T09:54:51.918Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/d9257dd49ff2ca23ea5f132edf1281a0c4f9de8a762b9ae399b670a59235/typer-0.21.1-py3-none-any.whl", hash = "sha256:7985e89081c636b88d172c2ee0cfe33c253160994d47bdfdc302defd7d1f1d01", size = 47381, upload-time = "2026-01-06T11:21:09.824Z" }, ] [[package]] @@ -2468,14 +2469,14 @@ wheels = [ [[package]] name = "types-requests" -version = "2.32.4.20250913" +version = "2.32.4.20260107" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/36/27/489922f4505975b11de2b5ad07b4fe1dca0bca9be81a703f26c5f3acfce5/types_requests-2.32.4.20250913.tar.gz", hash = "sha256:abd6d4f9ce3a9383f269775a9835a4c24e5cd6b9f647d64f88aa4613c33def5d", size = 23113, upload-time = "2025-09-13T02:40:02.309Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/f3/a0663907082280664d745929205a89d41dffb29e89a50f753af7d57d0a96/types_requests-2.32.4.20260107.tar.gz", hash = "sha256:018a11ac158f801bfa84857ddec1650750e393df8a004a8a9ae2a9bec6fcb24f", size = 23165, upload-time = "2026-01-07T03:20:54.091Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/20/9a227ea57c1285986c4cf78400d0a91615d25b24e257fd9e2969606bdfae/types_requests-2.32.4.20250913-py3-none-any.whl", hash = "sha256:78c9c1fffebbe0fa487a418e0fa5252017e9c60d1a2da394077f1780f655d7e1", size = 20658, upload-time = "2025-09-13T02:40:01.115Z" }, + { url = "https://files.pythonhosted.org/packages/1c/12/709ea261f2bf91ef0a26a9eed20f2623227a8ed85610c1e54c5805692ecb/types_requests-2.32.4.20260107-py3-none-any.whl", hash = "sha256:b703fe72f8ce5b31ef031264fe9395cac8f46a04661a79f7ed31a80fb308730d", size = 20676, upload-time = "2026-01-07T03:20:52.929Z" }, ] [[package]] @@ -2522,11 +2523,11 @@ wheels = [ [[package]] name = "urllib3" -version = "2.6.2" +version = "2.6.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1e/24/a2a2ed9addd907787d7aa0355ba36a6cadf1768b934c652ea78acbd59dcd/urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797", size = 432930, upload-time = "2025-12-11T15:56:40.252Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182, upload-time = "2025-12-11T15:56:38.584Z" }, + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, ] [[package]] @@ -2599,6 +2600,7 @@ dev = [ { name = "ty" }, ] test = [ + { name = "hypothesis", extra = ["numpy"] }, { name = "pytest" }, ] types = [ @@ -2612,6 +2614,7 @@ types = [ requires-dist = [ { name = "deptry", marker = "extra == 'dev'" }, { name = "geopandas" }, + { name = "hypothesis", extras = ["numpy"], marker = "extra == 'test'", specifier = ">=6.0.0" }, { name = "im-calculation" }, { name = "nshmdb", specifier = ">=2025.12.1" }, { name = "numexpr" }, @@ -2621,7 +2624,7 @@ requires-dist = [ { name = "pandas", extras = ["hdf5", "parquet"] }, { name = "pandas-stubs", marker = "extra == 'types'" }, { name = "psutil" }, - { name = "pytest", marker = "extra == 'test'" }, + { name = "pytest", marker = "extra == 'test'", specifier = ">=6.0.0" }, { name = "pyyaml" }, { name = "qcore-utils", specifier = ">=2025.12.1" }, { name = "requests" }, diff --git a/workflow/default_parameters/develop/defaults.yaml b/workflow/default_parameters/develop/defaults.yaml index a4bc598..60897de 100644 --- a/workflow/default_parameters/develop/defaults.yaml +++ b/workflow/default_parameters/develop/defaults.yaml @@ -130,8 +130,73 @@ velocity_model: version: "2.07" topo_type: "SQUASHED_TAPERED" vs30: 500.0 + fault_buffer: 2000.0 s_wave_velocity: 3500.0 - pgv_interpolants: [[3.5, 1.0], [8., 1.0]] + rrup_interpolants: + [ + [ + 3.5, + 3.6, + 3.7, + 3.8, + 3.9, + 4., + 4.1, + 4.2, + 4.3, + 4.4, + 4.5, + 4.6, + 4.7, + 4.8, + 4.9, + 5., + 5.1, + 5.2, + 5.3, + 5.4, + 5.5, + 5.6, + 5.7, + 5.8, + 6., + 6.5, + 7., + 7.5, + 8., + ], + [ + 96.001584, + 95.96318337, + 98., + 102., + 108.02690269, + 114.86856676, + 123.44523863, + 128.68959965, + 134.58683383, + 145.68111725, + 157.68992642, + 170.68864766, + 188.45405921, + 192.22314039, + 203.98873437, + 216.47447683, + 233.19667576, + 248.66112894, + 258.19003824, + 268.72840715, + 280.03281854, + 297.1730673, + 300., + 300., + 300., + 300., + 300., + 300., + 300., + ], + ] velocity_model_1d: model: - thickness: 0.0500 diff --git a/workflow/default_parameters/v24_2_2_1/defaults.yaml b/workflow/default_parameters/v24_2_2_1/defaults.yaml index 5a925cd..6bc4aff 100644 --- a/workflow/default_parameters/v24_2_2_1/defaults.yaml +++ b/workflow/default_parameters/v24_2_2_1/defaults.yaml @@ -131,7 +131,72 @@ velocity_model: topo_type: "SQUASHED_TAPERED" vs30: 500.0 s_wave_velocity: 3500.0 - pgv_interpolants: [[3.5, 1.0], [8., 1.0]] + fault_buffer: 2000.0 + rrup_interpolants: + [ + [ + 3.5, + 3.6, + 3.7, + 3.8, + 3.9, + 4., + 4.1, + 4.2, + 4.3, + 4.4, + 4.5, + 4.6, + 4.7, + 4.8, + 4.9, + 5., + 5.1, + 5.2, + 5.3, + 5.4, + 5.5, + 5.6, + 5.7, + 5.8, + 6., + 6.5, + 7., + 7.5, + 8., + ], + [ + 96.001584, + 95.96318337, + 98., + 102., + 108.02690269, + 114.86856676, + 123.44523863, + 128.68959965, + 134.58683383, + 145.68111725, + 157.68992642, + 170.68864766, + 188.45405921, + 192.22314039, + 203.98873437, + 216.47447683, + 233.19667576, + 248.66112894, + 258.19003824, + 268.72840715, + 280.03281854, + 297.1730673, + 300., + 300., + 300., + 300., + 300., + 300., + 300., + ], + ] velocity_model_1d: model: - thickness: 0.0500 diff --git a/workflow/default_parameters/v24_2_2_2/defaults.yaml b/workflow/default_parameters/v24_2_2_2/defaults.yaml index 797d080..15afb3c 100644 --- a/workflow/default_parameters/v24_2_2_2/defaults.yaml +++ b/workflow/default_parameters/v24_2_2_2/defaults.yaml @@ -130,8 +130,73 @@ velocity_model: version: "2.07" topo_type: "SQUASHED_TAPERED" vs30: 500.0 + fault_buffer: 2000.0 s_wave_velocity: 3500.0 - pgv_interpolants: [[3.5, 1.0], [8., 1.0]] + rrup_interpolants: + [ + [ + 3.5, + 3.6, + 3.7, + 3.8, + 3.9, + 4., + 4.1, + 4.2, + 4.3, + 4.4, + 4.5, + 4.6, + 4.7, + 4.8, + 4.9, + 5., + 5.1, + 5.2, + 5.3, + 5.4, + 5.5, + 5.6, + 5.7, + 5.8, + 6., + 6.5, + 7., + 7.5, + 8., + ], + [ + 96.001584, + 95.96318337, + 98., + 102., + 108.02690269, + 114.86856676, + 123.44523863, + 128.68959965, + 134.58683383, + 145.68111725, + 157.68992642, + 170.68864766, + 188.45405921, + 192.22314039, + 203.98873437, + 216.47447683, + 233.19667576, + 248.66112894, + 258.19003824, + 268.72840715, + 280.03281854, + 297.1730673, + 300., + 300., + 300., + 300., + 300., + 300., + 300., + ], + ] bb: flo: 0.5 fmidbot: 0.5 diff --git a/workflow/default_parameters/v24_2_2_4/defaults.yaml b/workflow/default_parameters/v24_2_2_4/defaults.yaml index f37933e..0d97d2a 100644 --- a/workflow/default_parameters/v24_2_2_4/defaults.yaml +++ b/workflow/default_parameters/v24_2_2_4/defaults.yaml @@ -130,8 +130,73 @@ velocity_model: version: "2.07" topo_type: "SQUASHED_TAPERED" vs30: 500.0 + fault_buffer: 2000.0 s_wave_velocity: 3500.0 - pgv_interpolants: [[3.5, 1.0], [8., 1.0]] + rrup_interpolants: + [ + [ + 3.5, + 3.6, + 3.7, + 3.8, + 3.9, + 4., + 4.1, + 4.2, + 4.3, + 4.4, + 4.5, + 4.6, + 4.7, + 4.8, + 4.9, + 5., + 5.1, + 5.2, + 5.3, + 5.4, + 5.5, + 5.6, + 5.7, + 5.8, + 6., + 6.5, + 7., + 7.5, + 8., + ], + [ + 96.001584, + 95.96318337, + 98., + 102., + 108.02690269, + 114.86856676, + 123.44523863, + 128.68959965, + 134.58683383, + 145.68111725, + 157.68992642, + 170.68864766, + 188.45405921, + 192.22314039, + 203.98873437, + 216.47447683, + 233.19667576, + 248.66112894, + 258.19003824, + 268.72840715, + 280.03281854, + 297.1730673, + 300., + 300., + 300., + 300., + 300., + 300., + 300., + ], + ] velocity_model_1d: model: - thickness: 0.0500 diff --git a/workflow/realisations.py b/workflow/realisations.py index 24b3d83..86fb883 100644 --- a/workflow/realisations.py +++ b/workflow/realisations.py @@ -642,8 +642,10 @@ class VelocityModelParameters(RealisationConfiguration): """The reference vs30 value for duration estimation.""" s_wave_velocity: float """The s-wave velocity.""" - pgv_interpolants: npt.NDArray[np.float32] - """Target PGV values at specific magnitudes, used to estimate domain size.""" + rrup_interpolants: npt.NDArray[np.float32] + """Target RRup values at specific magnitudes, used to estimate domain size.""" + fault_buffer: float + """Buffer width (km) around sources in rupture. Domain edge is guaranteed not be within this distance from any source.""" def to_dict(self) -> dict: """ @@ -655,7 +657,7 @@ def to_dict(self) -> dict: Dictionary representation of the object. """ _dict = dataclasses.asdict(self) - _dict["pgv_interpolants"] = _dict["pgv_interpolants"].tolist() + _dict["rrup_interpolants"] = _dict["rrup_interpolants"].tolist() return _dict diff --git a/workflow/schemas.py b/workflow/schemas.py index 3538feb..69edccb 100644 --- a/workflow/schemas.py +++ b/workflow/schemas.py @@ -513,9 +513,12 @@ def _corners_to_array(corners_spec: list[dict[str, float]]) -> np.ndarray: ), Literal("vs30", "VS30 value"): And(NUMBER, _is_positive), Literal("s_wave_velocity", "S-wave velocity"): And(NUMBER, _is_positive), - Literal("pgv_interpolants", "PGV interpolants to estimate domain size"): And( + Literal("rrup_interpolants", "RRup interpolants to estimate domain size"): And( [[And(NUMBER, _is_positive)]], Use(np.array) ), + Literal("fault_buffer", "Buffer width (km) around sources in rupture."): And( + NUMBER, _is_positive + ), } ) diff --git a/workflow/scripts/generate_domain.py b/workflow/scripts/generate_domain.py new file mode 100644 index 0000000..e52dc1b --- /dev/null +++ b/workflow/scripts/generate_domain.py @@ -0,0 +1,610 @@ +#!/usr/bin/env python3 +"""Domain Generation. + +Description +----------- +Find a suitable simulation domain, estimating a rupture radius that captures significant ground motion, and the time the simulation should run for to capture this ground motion. + +Inputs +------ +A realisation file containing a metadata configuration, source definitions and rupture propagation information. + +Outputs +------- +A realisation file containing velocity model and domain extent parameters. + +Environment +----------- +Can be run in the cybershake container. Can also be run from your own computer using the `generate-domain` command which is installed after running `pip install workflow@git+https://github.com/ucgmsim/workflow`. + +Usage +----- +`generate-domain [OPTIONS] REALISATION_FFP` + +For More Help +------------- +See the output of `generate-domain --help` or `workflow.scripts.generate_domain`. +""" + +from collections.abc import Iterable +from dataclasses import dataclass +from pathlib import Path +from typing import Annotated + +import numpy as np +import numpy.typing as npt +import pandas as pd +import shapely +import typer + +from qcore import cli, geo +from source_modelling import moment, sources +from velocity_modelling import bounding_box +from velocity_modelling.bounding_box import BoundingBox +from workflow import log_utils, realisations, utils +from workflow.realisations import ( + DomainParameters, + Magnitudes, + Rakes, + RealisationMetadata, + SourceConfig, + VelocityModelParameters, +) + +app = typer.Typer() + + +def get_significant_duration( + magnitude: float, distance: float, vs30: float, rake: float, z1pt0: float +) -> float: + """Estimate significant duration using Afshari-Stewart (2016). + + Parameters + ---------- + magnitude : float + The magnitude of the rupture. + distance : float + The rupture distance (rrup) in kilometers to estimate Ds595 for. + vs30 : float + The Vs30 value at the site. + rake : float + The rake parameter. + z1pt0 : float + The Z1.0 estimated at the site. + + Returns + ------- + float + The estimated Ds595 of the rupture (in seconds). + """ + + import oq_wrapper as oqw + + # Create the input context for the GMM + ctx = pd.DataFrame( + { + "vs30": [vs30], + "z1pt0": [z1pt0], + "rrup": [distance], + "mag": [magnitude], + "rake": [rake], + } + ) + + # Execute and transform from log space + results = oqw.run_gmm( + oqw.constants.GMM.AS_16, + oqw.constants.TectType.ACTIVE_SHALLOW, + ctx, + "Ds595", + ) + + return np.exp(results["Ds595_mean"].iloc[0]) + + +def boundary_distance( + domain: BoundingBox, sources: Iterable[sources.IsSource] +) -> float: + r"""Compute the distance from source to boundary. + + The largest distance between the fault geometry and the domain + boundary is the Hausdorff distance, that is max_{x \in F_g} + (min_{y \in D} d(x, y)) where F_g is the fault geometry, and D is + the domain boundary. + + Parameters + ---------- + domain : BoundingBox + The domain bounding box. + sources : Iterable[sources.IsSource] + The sources to compute the boundary distance for. + + Returns + ------- + float + The Hausdorff distance between the sources and the boundary (in meters). + """ + source_geometry = shapely.union_all([source.geometry for source in sources]) + bounding_box_geometry = domain.polygon + return float(shapely.hausdorff_distance(source_geometry, bounding_box_geometry)) + + +def total_magnitude(magnitudes: Iterable[float]) -> float: + """ + Compute the total magnitude from an array of individual magnitudes. + + Parameters + ---------- + magnitudes : Iterable[float] + An array of magnitudes. + + Returns + ------- + float + The total magnitude, computed from the summed moment of the input magnitudes. + """ + total_moment = sum( + moment.magnitude_to_moment(magnitude) for magnitude in magnitudes + ) + return moment.moment_to_magnitude(total_moment) + + +@dataclass +class RuptureContext: + """Rupture context container class.""" + + magnitude: float + """Rupture magnitude.""" + rake: float + """Rupture rake.""" + vs30: float + """Rupture vs30.""" + z1pt0: float + """Rupture Z1.0.""" + s_wave_velocity: float + """Rupture s-wave velocity.""" + ds_multiplier: float + """Rupture Ds multiplier.""" + + +def rupture_context_from( + magnitudes: Magnitudes, + rakes: Rakes, + velocity_model_parameters: VelocityModelParameters, +) -> RuptureContext: + """Create a rupture context from a rupture. + + Parameters + ---------- + magnitudes : Magnitudes + The magnitudes of the rupture faults. + rakes : Rakes + The rakes in the rupture faults. + velocity_model_parameters : VelocityModelParameters + The velocity model parameters. Only use s_wave_velocity and + ds_multiplier. + + Returns + ------- + RuptureContext + The rupture context computed from the source information and + velocity model parameters. + """ + import oq_wrapper as oqw + + magnitude = total_magnitude(magnitudes.magnitudes.values()) + rake = average_rake(rakes, magnitudes) + + z1pt0 = float( + oqw.estimations.chiou_young_08_calc_z1p0(velocity_model_parameters.vs30) + ) + + s_wave_velocity = velocity_model_parameters.s_wave_velocity + ds_multiplier = velocity_model_parameters.ds_multiplier + + return RuptureContext( + magnitude=magnitude, + rake=rake, + vs30=velocity_model_parameters.vs30, + z1pt0=z1pt0, + s_wave_velocity=s_wave_velocity, + ds_multiplier=ds_multiplier, + ) + + +def average_rake(rakes: Rakes, magnitudes: Magnitudes) -> float: + """Find moment-weighted average rupture rake. + + Parameters + ---------- + rakes : Rakes + The rakes to average. + magnitudes : Magnitudes + The magnitudes to weight rakes by. + + Returns + ------- + float + The moment-weighted average rake. + """ + moments = { + k: moment.magnitude_to_moment(magnitude) + for k, magnitude in magnitudes.magnitudes.items() + } + max_moment = max(moments.values()) + # re-normalise for debugging purposes and to maybe improve + # floating-point accuracy in the avg wbearing function. + moments = {k: moment / max_moment for k, moment in moments.items()} + + weighted_rakes = list(utils.dict_zip(rakes.rakes, moments).values()) + return geo.avg_wbearing(weighted_rakes) # type: ignore[invalid-argument-type] + + +def estimate_simulation_duration( + rupture_context: RuptureContext, + bounding_box: BoundingBox, + faults: Iterable[sources.IsSource], +) -> float: + """Estimate the simulation duration required for a realisation in a given domain. + + The simulation duration is the length of time it + takes the S-waves to reach and pass the edge of the domain from + the centre of the fault(s). + + Parameters + ---------- + rupture_context : RuptureContext + The rupture context. + bounding_box : BoundingBox + The domain to estimate boundary distances for. + faults : Iterable[sources.IsSource] + The faults to estimate boundary distances for. + + Returns + ------- + float + The estimated simulation duration time (in seconds). + """ + largest_distance = boundary_distance(bounding_box, faults) + + significant_duration = get_significant_duration( + distance=largest_distance / 1000.0, + magnitude=rupture_context.magnitude, + vs30=rupture_context.vs30, + rake=rupture_context.rake, + z1pt0=rupture_context.z1pt0, + ) + + s_wave_arrival_time = largest_distance / rupture_context.s_wave_velocity + total_duration = s_wave_arrival_time + ( + significant_duration * rupture_context.ds_multiplier + ) + + return total_duration + + +def simulation_max_depth(magnitude: float, bottom_depth: float) -> float: + """Estimate the maximum depth to simulate for a rupture. + + This function estimates the max depth of the domain by ensuring + the z-extent is deep enough so the bottom does not interfere with + waves initially radiating downwards from the earthquake before + returning to the surface. Its design is, + + 1. The bottom depth of the rupture plus + 2. a flat 10km buffer and, + 3. a magnitude dependent power term that accounts for the fact + that waves refract deeper from larger magnitude earthquakes, but + 4. accounting for the fact that deeper earthquakes for the same + magnitude result in weaker shaking at the surface. + + Parameters + ---------- + magnitude : float + The magnitude of the rupture. + bottom_depth : float + Bottom depth (in km). + + Returns + ------- + float + The maximum simulation depth. + + References + ---------- + Robin's slack message that describes the max depth estimation: + https://uceqeng.slack.com/archives/C06L1MRUQF8/p1718865771998809?thread_ts=1718846081.570789&cid=C06L1MRUQF8. + """ + return round( + 10 + + bottom_depth + + ( + 10 + * np.power( + (0.5 * np.power(10, (0.55 * magnitude - 1.2)) / bottom_depth), 0.3 + ) + ), + ndigits=0, # like default rounding behaviour but returns a float. + ) + + +def estimate_r_surface( + rrup_interpolants: npt.NDArray[np.floating], magnitude: float, ztor: float +) -> float: + """Estimate the horizontal surface distance from a rupture. + + Parameters + ---------- + rrup_interpolants : array of floats + A 2D array where the first row contains magnitude values and the second + row contains the corresponding rrup values (in km). + magnitude : float + The magnitude of the rupture. + ztor : float + The depth to the top of the rupture plane (in km). + + Returns + ------- + float + The estimated horizontal surface distance (in metres). + """ + rrup = float(np.interp(magnitude, rrup_interpolants[0], rrup_interpolants[1])) + + if rrup < ztor: + raise ValueError( + f"Top-of-rupture depth {ztor=} is greater than estimated rrup {rrup=}." + ) + + r_surface = np.sqrt(rrup**2 - ztor**2) + return r_surface * 1000 + + +def fault_top(fault: sources.IsSource) -> float: + """Return the top of rupture distance (ztor). + + Parameters + ---------- + fault : sources.IsSource + The fault to compute rupture top for. + + Returns + ------- + float + The top-depth of the rupture in km. + """ + if isinstance(fault, sources.Point): + # TODO: backport this into source modelling + ztor = fault.centroid[2] - fault.width_m / 2 * np.sin(np.radians(fault.dip)) + else: + ztor = fault.top_m + ztor /= 1000.0 + return ztor + + +def find_r_surfaces( + source_config: SourceConfig, + magnitudes: Magnitudes, + rrup_interpolants: npt.NDArray[np.floating], +) -> dict[str, float]: + """Find r_surfaces for all sources. + + Parameters + ---------- + source_config : SourceConfig + The sources to find r_surfaces for. + magnitudes : Magnitudes + The magnitudes of the rupture on each source. + rrup_interpolants : array of floats + The rrup function of magnitude. + + Returns + ------- + dict[str, float] + A key-value mapping of source name to r_surface. + """ + return { + fault_name: estimate_r_surface(rrup_interpolants, magnitude, fault_top(fault)) + for fault_name, (fault, magnitude) in utils.dict_zip( + source_config.source_geometries, + magnitudes.magnitudes, + ).items() + } + + +def source_max_depth(faults: Iterable[sources.IsSource]) -> float: + """Find the max depth of the rupture sources. + + Parameters + ---------- + faults : Iterable[sources.IsSource] + The faults to find depths for. + + Returns + ------- + float + The maximum source depth of the rupture, in meters. + """ + depths: list[float] = [] + + for fault in faults: + if isinstance(fault, sources.Point): + # TODO: backport this into source modelling + bottom_m = fault.centroid[2] + fault.width_m / 2 * np.sin( + np.radians(fault.dip) + ) + else: + bottom_m = fault.bottom_m + depths.append(bottom_m) + + return max(depths) + + +def estimate_domain( + source_config: SourceConfig, + rrups: dict[str, float], + nz_outline: shapely.Geometry, + fault_buffer: float, +) -> BoundingBox: + """Estimate a domain for a rupture. + + Parameters + ---------- + source_config : SourceConfig + The sources in the rupture. + rrups : dict[str, float] + The rrups for each source. + nz_outline : Geometry + The NZ outline polygon. + fault_buffer : float + Buffer width (km) around sources in rupture. Domain edge is + guaranteed not be within this distance from any source. + + Returns + ------- + BoundingBox + The smallest domain containing the sources and areas within + the estimated surface distance for the given magnitudes. + """ + # This polygon includes all the faults corners + a variable + # (default 2km) fault buffer. This polygon must be included in the + # domain. + fault_buffer_polygons = [ + shapely.buffer(fault.geometry, fault_buffer * 1000.0) + for fault in source_config.source_geometries.values() + ] + + rrup_bounding_polygons = [ + shapely.buffer(fault.geometry, rrup) + for fault, rrup in utils.dict_zip( + source_config.source_geometries, rrups + ).values() + ] + + # The domain is the minimum area bounding box containing all of + # the fault corners, and all points on land within rrup distance + # of a fault corner. + model_domain = bounding_box.minimum_area_bounding_box_for_polygons_masked( + must_include=fault_buffer_polygons, + may_include=rrup_bounding_polygons, + mask=nz_outline, # type: ignore[invalid-argument-type] + ) + return model_domain + + +def domain_max_depth( + source_config: SourceConfig, + magnitudes: Magnitudes, +) -> float: + """Estimate the maximum reasonable simulation depth. + + Parameters + ---------- + source_config : SourceConfig + The faults in the rupture. + magnitudes : Magnitudes + The magnitudes of each rupture. + + Returns + ------- + float + The estimated maximum reasonable simulation depth, in kilometers. + """ + max_depth_km = source_max_depth(source_config.source_geometries.values()) / 1000 + magnitude = total_magnitude(magnitudes.magnitudes.values()) + + return simulation_max_depth(magnitude, max_depth_km) + + +def generate_domain( + source_config: SourceConfig, + magnitudes: Magnitudes, + rakes: Rakes, + velocity_model_parameters: VelocityModelParameters, +) -> DomainParameters: + """ + Computes simulation domain spatial extent and temporal duration. + + Parameters + ---------- + source_config : SourceConfig + Configuration containing the geometries for all faults involved in + the realisation. + magnitudes : Magnitudes + The magnitudes associated with each source in the realisation. + rakes : Rakes + The rake angles for the source geometries. + velocity_model_parameters : VelocityModelParameters + Parameters defining the velocity model, including Vs30, S-wave + velocity, and the duration scaling multiplier. + + Returns + ------- + DomainParameters + An object containing the computed model domain (bounding box), + the maximum simulation depth, and the estimated simulation duration. + """ + + rupture_context = rupture_context_from(magnitudes, rakes, velocity_model_parameters) + + rrups = find_r_surfaces( + source_config, magnitudes, velocity_model_parameters.rrup_interpolants + ) + nz_outline = utils.get_nz_outline_polygon() + model_domain = estimate_domain( + source_config, rrups, nz_outline, velocity_model_parameters.fault_buffer + ) + sim_duration = estimate_simulation_duration( + rupture_context, model_domain, source_config.source_geometries.values() + ) + depth = domain_max_depth(source_config, magnitudes) + + domain_parameters = DomainParameters( + domain=model_domain, + depth=depth, + duration=sim_duration, + ) + return domain_parameters + + +@cli.from_docstring(app) +@log_utils.log_call() +def generate_domain_from_realisation( + realisation_ffp: Annotated[Path, typer.Argument()], +) -> None: + """Generate domain parameters for a given realisation file. + + This function reads the source, rupture propagation, and velocity model + information and computes: + + 1. The size of the simulation domain, + 2. The simulation duration. + + Both of these values are written to the realisation using `DomainParameters`. + + Parameters + ---------- + realisation_ffp : Path + The path to the realisation file from which to read configurations and to which + the generated domain parameters will be written. + + Returns + ------- + None + The function does not return any value. It writes the computed parameters to + the specified realisation file. + """ + metadata = RealisationMetadata.read_from_realisation(realisation_ffp) + source_config = SourceConfig.read_from_realisation(realisation_ffp) + velocity_model_parameters = ( + VelocityModelParameters.read_from_realisation_or_defaults( + realisation_ffp, metadata.defaults_version + ) + ) + + magnitudes = Magnitudes.read_from_realisation(realisation_ffp) + rakes = Rakes.read_from_realisation(realisation_ffp) + domain_parameters = generate_domain( + source_config, magnitudes, rakes, velocity_model_parameters + ) + domain_parameters.write_to_realisation(realisation_ffp) + realisations.append_log_entry(realisation_ffp) diff --git a/workflow/scripts/generate_velocity_model_parameters.py b/workflow/scripts/generate_velocity_model_parameters.py deleted file mode 100644 index 790240f..0000000 --- a/workflow/scripts/generate_velocity_model_parameters.py +++ /dev/null @@ -1,525 +0,0 @@ -#!/usr/bin/env python3 -"""Domain Generation. - -Description ------------ -Find a suitable simulation domain, estimating a rupture radius that captures significant ground motion, and the time the simulation should run for to capture this ground motion. - -Inputs ------- -A realisation file containing a metadata configuration, source definitions and rupture propagation information. - -Outputs -------- -A realisation file containing velocity model and domain extent parameters. - -Environment ------------ -Can be run in the cybershake container. Can also be run from your own computer using the `generate-velocity-model-parameters` command which is installed after running `pip install workflow@git+https://github.com/ucgmsim/workflow`. - -Usage ------ -`generate-velocity-model-parameters [OPTIONS] REALISATION_FFP` - -For More Help -------------- -See the output of `generate-velocity-model-parameters --help` or `workflow.scripts.generate_velocity_model_parameters`. -""" - -from collections.abc import Mapping -from pathlib import Path -from typing import Annotated, Any, TypeVar - -import numpy as np -import numpy.typing as npt -import pandas as pd -import scipy as sp -import shapely -import typer -from shapely import Polygon - -from qcore import cli -from qcore.uncertainties import mag_scaling -from source_modelling import sources -from velocity_modelling import bounding_box -from velocity_modelling.bounding_box import BoundingBox -from workflow import log_utils, realisations, utils -from workflow.realisations import ( - DomainParameters, - Magnitudes, - Rakes, - RealisationMetadata, - RupturePropagationConfig, - SourceConfig, - VelocityModelParameters, -) - -app = typer.Typer() - - -@log_utils.log_call(exclude_args=["faults"]) -def estimate_simulation_duration( - bounding_box: BoundingBox, - magnitude: float, - faults: list[sources.IsSource], - rakes: npt.NDArray[np.float64], - ds_multiplier: float, - vs30: float, - s_wave_velocity: float, -) -> float: - """Estimate the simulation duration required for a realisation in a given domain. - - The simulation distance is the length of time it - takes the S-waves to reach and pass the edge of the domain from - the centre of the fault(s). - - Parameters - ---------- - bounding_box : BoundingBox - The bounding box representing the simulation domain. - magnitude : float - The magnitude of the earthquake rupture. - faults : list of sources.IsSource - A list of fault objects defining the fault geometries. - rakes : np.ndarray - An array of rake angles for the faults. - ds_multiplier : float - Multiplier for the wavelength of the s-wave to adjust simulation duration. - vs30 : float - Average shear-wave velocity in the top 30 meters of soil (in m/s). - s_wave_velocity : float - Shear-wave velocity (in m/s) used to compute the travel time. - - Returns - ------- - float - The estimated simulation duration time (in seconds). - """ - - # compute the largest distance between the fault geometry and the domain boundary, that is - # - # max_{x \in F_g} (min_{y \in D} d(x, y)) - # - # where F_g is the fault geometry, and D is the domain boundary. - largest_distance = ( - shapely.hausdorff_distance( - shapely.union_all([fault.geometry for fault in faults]), - bounding_box.polygon, - ) - / 1000 - ) - - s_wave_arrival_time = (largest_distance * 1000) / s_wave_velocity - - # import here rather than at the module level because openquake is slow to import - import oq_wrapper as oqw - - avg_rake = np.mean(rakes) - - oq_dataframe = pd.DataFrame.from_dict( - { - "vs30": [vs30], - "z1pt0": [oqw.estimations.chiou_young_08_calc_z1p0(vs30)], - "rrup": [largest_distance], - "mag": [magnitude], - "rake": [avg_rake], - } - ) - - ds = np.exp( - oqw.run_gmm( - oqw.constants.GMM.AS_16, - oqw.constants.TectType.ACTIVE_SHALLOW, - oq_dataframe, - "Ds595", - )["Ds595_mean"].iloc[0] - ) - - log_utils.get_logger(__name__).debug( - "Computing simulation duration with parameters", - vs30=vs30, - largest_distance=largest_distance, - s_wave_velocity=s_wave_velocity, - s_wave_arrival_time=s_wave_arrival_time, - ds_multiplier=ds_multiplier, - magnitude=magnitude, - rakes=rakes, - ds=ds, - avg_rake=avg_rake, - ) - - return s_wave_arrival_time + ds_multiplier * ds - - -def get_max_depth(magnitude: float, hypocentre_depth: float) -> int: - """Estimate the maximum depth to simulate for a rupture. - - Parameters - ---------- - magnitude : float - The magnitude of the rupture. - hypocentre_depth : float - hypocentre depth (in km). - - Returns - ------- - float - The maximum simulation depth. - - References - ---------- - See the "Custom Models Used in VM Params" wiki page for an explanation of this function. - """ - return round( - 10 - + hypocentre_depth - + ( - 10 - * np.power( - (0.5 * np.power(10, (0.55 * magnitude - 1.2)) / hypocentre_depth), 0.3 - ) - ), - 0, - ) - - -def total_magnitude(magnitudes: npt.NDArray[np.float64]) -> float: - """ - Compute the total magnitude from an array of individual magnitudes. - - Parameters - ---------- - magnitudes : np.ndarray - An array of magnitudes. - - Returns - ------- - float - The total magnitude, computed from the summed moment of the input magnitudes. - """ - return mag_scaling.mom2mag(np.sum(mag_scaling.mag2mom(magnitudes))) - - -def pgv_from_rrup( - magnitude: float, rake: float, dip: float, rrup: float, ztor: float -) -> float: - """ - Compute the peak ground velocity (PGV) at a given distance from a rupture. - - Parameters - ---------- - magnitude : float - The magnitude of the rupture. - rake : float - The rake angle of the rupture. - dip : float - The dip angle of the rupture. - rrup : float - The distance from the rupture (in km). - ztor : float - The distance to the top of the fault geometry. - - Returns - ------- - float - The peak ground velocity (cm/s) at the given distance from the rupture. - """ - # import here rather than at the module level because openquake is slow to import - import oq_wrapper as oqw - - vs30 = 500 # default Vs30 value - return np.exp( - oqw.run_gmm( - oqw.constants.GMM.CY_14, - oqw.constants.TectType.ACTIVE_SHALLOW, - pd.DataFrame( - { - "mag": [magnitude], - "rake": [rake], - "vs30": [vs30], - "vs30measured": [False], - "dip": [dip], - "z1pt0": [oqw.estimations.chiou_young_08_calc_z1p0(vs30)], - # These calculations are done with a point-source - # assumption. We don't know where our test point is so - # estimating them from source geometry is impossible - # since rjb depends on polygon-distance measurements. - # We believe this is defensible for reasons: - # - # 1. At small Mw, we assume a point-source anyway so these calculations are essentially correct. - # 2. At large Mw, PGV of 0.1cm/s will occur sufficiently far from the event that a point-source approximation is reasonable. - "ztor": [ztor], - "rrup": [rrup], - "rjb": [np.sqrt(np.maximum(0, rrup**2 - ztor**2))], - # We want to include any hanging-wall terms in the model - # to err on the conservative side for our domains. In the - # other case, we risk shrinking our domains unnecessarily. - "rx": [rrup], - } - ), - "PGV", - )["PGV_mean"].iloc[0] - ) - - -@log_utils.log_call() -def estimate_rrup( - magnitude: float, rake: float, dip: float, ztor: float, pgv_target: float -) -> float: - """ - Estimate the rupture radius such that stations at this radius will - experience the target PGV. - - Parameters - ---------- - magnitude : float - The magnitude of the rupture. - rake : float - The rake angle of the rupture. - dip : float - The dip angle of the rupture. - ztor : float - The distance to the top of the fault geometry. - pgv_target : float - The target PGV value (cm/s). - - Returns - ------- - float - The estimated rupture radius (in km). - - Examples - -------- - >>> # Estimate the rupture radius for a 7.5 magnitude earthquake - >>> # with a rake of 90 degrees, a dip of 45 degrees, and a target - >>> # PGV of 10 cm/s. - >>> estimate_rrup(7.5, 90, 45, 10) - 60.86630588572306 - """ - return float( - sp.optimize.minimize_scalar( - lambda rrup: np.abs( - pgv_from_rrup(magnitude, rake, dip, rrup, ztor) - pgv_target - ), - bounds=(0, 1000), - method="bounded", - ).x - ) - - -def find_rrup_bounding_polygon( - fault: sources.IsSource, - magnitude: float, - rake: float, - pgv_target: float, -) -> Polygon: - """Find the bounding polygon for the rrup distance of a fault. - - The bounding polygon is computed by estimating rrup from the PGV - target, and then applying an rrup-width buffer to the fault - geometries. - - Parameters - ---------- - fault : sources.IsSource - The fault geometry. - magnitude : float - The magnitude of the rupture. - rake : float - The rake angle of the rupture. - pgv_target : float - The target PGV value (cm/s). - - Returns - ------- - Polygon - The bounding polygon over the rrup distance of the fault in the realisation. - """ - - if isinstance(fault, sources.Point): - # TODO: backport this into source modelling - ztor = fault.centroid[2] - fault.width_m / 2 * np.sin(np.radians(fault.dip)) - else: - ztor = fault.top_m - ztor /= 1000.0 - rrup = estimate_rrup( - magnitude, - rake, - fault.dip, - pgv_target, - ) - logger = log_utils.get_logger(__name__) - logger.debug("computed rrup", rrups=rrup) - - return shapely.buffer(fault.geometry, rrup * 1000) - - -K = TypeVar("K") - - -def dict_zip(*dicts: Mapping[K, Any], strict: bool = True) -> dict[K, tuple[Any, ...]]: - """ - Takes the product of one or more dictionaries. - - Parameters - ---------- - *dicts : list of dict - Variable number of dictionaries. - strict : bool, default False - If True, raise an error if the keys in `dicts` are not all the same. - - Returns - ------- - dict - A dictionary where each value is a tuple of the corresponding values from the input dictionaries. - - Raises - ------ - ValueError - If strict is True and the keys in the dictionaries are not all the same. - """ - if not dicts: - return {} - - keys: set[K] = set(dicts[0].keys()) - - if strict and any(set(d) != keys for d in dicts[1:]): - raise ValueError("Keys in dictionaries are not all the same.") - else: - for dict in dicts[1:]: - keys = keys.intersection(dict.keys()) - - result = {key: tuple(d[key] for d in dicts) for key in list(keys)} - return result - - -def pgv_target( - magnitudes: Magnitudes, - velocity_model_parameters: VelocityModelParameters, -) -> float: - """Compute the PGV target for the realisation. - - Parameters - ---------- - magnitudes : Magnitudes - The magnitudes object. - velocity_model_parameters : VelocityModelParameters - The velocity model parameters containing PGV interpolants. - - Returns - ------- - float - The PGV target for the realisation. - """ - total_magnitude = mag_scaling.mom2mag( - sum( - mag_scaling.mag2mom(magnitude) - for magnitude in magnitudes.magnitudes.values() - ) - ) - return float( - np.interp( - total_magnitude, - velocity_model_parameters.pgv_interpolants[:, 0], - velocity_model_parameters.pgv_interpolants[:, 1], - ) - ) - - -@cli.from_docstring(app) -@log_utils.log_call() -def generate_velocity_model_parameters( - realisation_ffp: Annotated[Path, typer.Argument()], -) -> None: - """Generate velocity model parameters for a given realisation file. - - This function reads the source and rupture propagation information and computes: - - 1. The size of the simulation domain, - 2. The simulation duration. - - Both of these values are written to the realisation using `VelocityModelParameters`. - - Parameters - ---------- - realisation_ffp : Path - The path to the realisation file from which to read configurations and to which - the generated velocity model parameters will be written. - - Returns - ------- - None - The function does not return any value. It writes the computed parameters to - the specified realisation file. - """ - metadata = RealisationMetadata.read_from_realisation(realisation_ffp) - source_config = SourceConfig.read_from_realisation(realisation_ffp) - velocity_model_parameters = ( - VelocityModelParameters.read_from_realisation_or_defaults( - realisation_ffp, metadata.defaults_version - ) - ) - - rupture_propagation = RupturePropagationConfig.read_from_realisation( - realisation_ffp - ) - magnitudes = Magnitudes.read_from_realisation(realisation_ffp) - rakes = Rakes.read_from_realisation(realisation_ffp) - rupture_magnitude = total_magnitude(np.array(list(magnitudes.magnitudes.values()))) - realisation_pgv_target = pgv_target(magnitudes, velocity_model_parameters) - - initial_fault = source_config.source_geometries[rupture_propagation.initial_fault] - if isinstance(initial_fault, sources.Point): - # TODO: backport this into source modelling - bottom_m = initial_fault.centroid[2] + initial_fault.width_m / 2 * np.sin( - np.radians(initial_fault.dip) - ) - else: - bottom_m = initial_fault.bottom_m - max_depth = get_max_depth(rupture_magnitude, bottom_m / 1000.0) - - # This polygon includes all the faults corners + a 2km buffer (which must be in the simulation domain). - fault_buffer_polygons = [ - shapely.buffer(fault.geometry, 2000) - for fault in source_config.source_geometries.values() - ] - rakes = Rakes.read_from_realisation(realisation_ffp) - # This polygon includes all areas within rrup distance of any - # corner in the source geometries. - # These may be in the domain where they are over land. - rrup_bounding_polygons = [ - # The type error about existing assignment seems to be a bug. - find_rrup_bounding_polygon(*args, pgv_target=realisation_pgv_target) # type: ignore - for args in dict_zip( - source_config.source_geometries, - magnitudes.magnitudes, - rakes.rakes, - ).values() - ] - - # The domain is the minimum area bounding box containing all of - # the fault corners, and all points on land within rrup distance - # of a fault corner. - 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(), # type: ignore - ) - - sim_duration = estimate_simulation_duration( - model_domain, - rupture_magnitude, - list(source_config.source_geometries.values()), - np.fromiter(rakes.rakes.values(), float), - velocity_model_parameters.ds_multiplier, - velocity_model_parameters.vs30, - velocity_model_parameters.s_wave_velocity, - ) - - domain_parameters = DomainParameters( - domain=model_domain, - depth=max_depth, - duration=sim_duration, - ) - domain_parameters.write_to_realisation(realisation_ffp) - realisations.append_log_entry(realisation_ffp) diff --git a/workflow/utils.py b/workflow/utils.py index 0b91984..3e3c627 100644 --- a/workflow/utils.py +++ b/workflow/utils.py @@ -3,6 +3,8 @@ import os import tempfile import urllib.request +from collections.abc import Mapping +from typing import Any, TypeVar, overload import geopandas as gpd import numpy as np @@ -101,3 +103,74 @@ def get_available_cores() -> int: return cpu_count else: raise RuntimeError("Cannot determine CPU count.") + + +K = TypeVar("K") +V1 = TypeVar("V1") +V2 = TypeVar("V2") +V3 = TypeVar("V3") + + +# These overloads provide better type inference in the common case +@overload +def dict_zip( + __d1: Mapping[K, V1], *, strict: bool = ... +) -> dict[K, tuple[V1]]: ... # numpydoc ignore=GL08 + + +@overload +def dict_zip( + __d1: Mapping[K, V1], __d2: Mapping[K, V2], *, strict: bool = ... +) -> dict[K, tuple[V1, V2]]: ... # numpydoc ignore=GL08 + + +@overload +def dict_zip( + __d1: Mapping[K, V1], + __d2: Mapping[K, V2], + __d3: Mapping[K, V3], + *, + strict: bool = ..., +) -> dict[K, tuple[V1, V2, V3]]: ... # numpydoc ignore=GL08 + + +@overload +def dict_zip( + *dicts: Mapping[K, Any], strict: bool = ... +) -> dict[K, tuple[Any, ...]]: ... # numpydoc ignore=GL08 + + +def dict_zip(*dicts: Mapping[K, Any], strict: bool = True) -> dict[K, tuple[Any, ...]]: + """ + Takes the product of one or more dictionaries. + + Parameters + ---------- + *dicts : list of dict + Variable number of dictionaries. + strict : bool, default True + If True, raise an error if the keys in `dicts` are not all the same. + + Returns + ------- + dict + A dictionary where each value is a tuple of the corresponding values from the input dictionaries. + + Raises + ------ + ValueError + If strict is True and the keys in the dictionaries are not all the same. + """ + if not dicts: + return {} + + keys: set[K] = set(dicts[0].keys()) + + if strict and any(set(d) != keys for d in dicts[1:]): + raise ValueError("Keys in dictionaries are not all the same.") + elif not strict: + for d in dicts[1:]: + keys = keys.intersection(d.keys()) + + result = {key: tuple(d[key] for d in dicts) for key in list(keys)} + return result