├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── custom.md │ └── feature_request.md └── pull_request_template.md ├── .gitignore ├── .sonarcloud.properties ├── .sonarlint └── connectedMode.json ├── .teamcity ├── Deploy │ └── DeployProject.kt ├── Dockerfile │ └── Dockerfile ├── Nightly │ └── NightlyProject.kt ├── Pixi │ └── PixiProject.kt ├── Templates │ ├── ExamplesTemplate.kt │ ├── GitHubIntegrationTemplate.kt │ ├── LintTemplate.kt │ ├── MyPyTemplate.kt │ ├── PipPythonTemplate.kt │ └── UnitTestsTemplate.kt ├── _Self │ └── MainProject.kt ├── pom.xml └── settings.kts ├── LICENSE ├── README.rst ├── docs ├── Makefile ├── _static │ ├── deltares-white.svg │ ├── deltares.js │ ├── deltares.svg │ ├── enabling-delta-life.svg │ ├── imod-python-logo-dark.svg │ └── imod-python-logo-light.svg ├── _templates │ ├── custom-class-template.rst │ └── custom-module-template.rst ├── api │ ├── changelog.rst │ ├── evaluate.rst │ ├── index.rst │ ├── io.rst │ ├── logging.rst │ ├── metamod.rst │ ├── mf6.rst │ ├── msw.rst │ ├── prepare.rst │ ├── select.rst │ ├── util.rst │ ├── visualize.rst │ └── wq.rst ├── clean.py ├── conf.py ├── developing │ ├── ci.rst │ ├── contributing.rst │ ├── docker.rst │ ├── documentation.rst │ ├── examples.rst │ ├── index.rst │ └── releasing.rst ├── examples │ ├── data │ │ └── .gitkeep │ ├── imod-wq │ │ └── .gitkeep │ ├── index.rst │ ├── mf6 │ │ └── .gitkeep │ └── visualize │ │ └── .gitkeep ├── faq │ ├── API-design.rst │ ├── general.rst │ ├── how-do-i │ │ ├── in-out.rst │ │ ├── index.rst │ │ ├── modification.rst │ │ ├── plot.rst │ │ └── unstructured.rst │ ├── imod5_backwards_compatibility.rst │ ├── index.rst │ ├── known-issues.rst │ ├── modeling.rst │ └── python.rst ├── index.rst ├── installation.rst ├── make.bat └── user-guide │ └── .gitkeep ├── examples ├── README.rst ├── imod-wq │ ├── Elder.py │ ├── FreshwaterLens.py │ ├── Henry-wq.py │ ├── Hydrocoin.py │ ├── README.rst │ ├── SaltwaterPocket.py │ └── VerticalInterface.py ├── metaswap │ ├── README.rst │ ├── metaswap_example.py │ └── read_metaswap_file.py ├── mf6 │ ├── Henry.py │ ├── README.rst │ ├── circle.py │ ├── circle_partitioned.py │ ├── circle_transport.py │ ├── different_ways_to_regrid_models.py │ ├── ex01_twri.py │ ├── example_1d_transport.py │ ├── example_models.py │ ├── hondsrug.py │ ├── hondsrug_partitioning.py │ ├── lake.py │ └── transport_2d.py ├── prepare │ ├── README.rst │ ├── point_interpolation.py │ ├── polygonize_raster.py │ ├── rasterize_shp.py │ └── reproject.py ├── user-guide │ ├── 00-index-examples.py │ ├── 01-raster-data.py │ ├── 02-vector-data.py │ ├── 03-timeseries-data.py │ ├── 05-unstructured-grids.py │ ├── 06-lazy-evaluation.py │ ├── 07-time-discretization.py │ ├── 08-regridding.py │ ├── 09-topsystem.py │ ├── 10-cleanup.py │ └── README.rst └── visualize │ ├── README.rst │ ├── flowvel_streamlines.py │ └── plot_map.py ├── imod ├── __init__.py ├── common │ ├── __init__.py │ ├── interfaces │ │ ├── __init__.py │ │ ├── idict.py │ │ ├── ilinedatapackage.py │ │ ├── imaskingsettings.py │ │ ├── imodel.py │ │ ├── ipackage.py │ │ ├── ipackagebase.py │ │ ├── ipointdatapackage.py │ │ ├── iregridpackage.py │ │ └── isimulation.py │ ├── statusinfo.py │ └── utilities │ │ ├── clip.py │ │ ├── grid.py │ │ ├── layer.py │ │ ├── line_data.py │ │ ├── mask.py │ │ ├── regrid.py │ │ ├── regrid_method_type.py │ │ ├── schemata.py │ │ └── value_filters.py ├── couplers │ ├── __init__.py │ └── metamod │ │ ├── __init__.py │ │ └── metamod.py ├── data │ ├── __init__.py │ ├── registry.txt │ ├── sample_data.py │ └── synthetic.py ├── evaluate │ ├── __init__.py │ ├── boundaries.py │ ├── budget.py │ ├── constraints.py │ ├── head.py │ └── streamfunction.py ├── formats │ ├── __init__.py │ ├── array_io │ │ ├── __init__.py │ │ ├── reading.py │ │ └── writing.py │ ├── gen │ │ ├── __init__.py │ │ └── gen.py │ ├── idf.py │ ├── ipf.py │ ├── prj │ │ ├── __init__.py │ │ └── prj.py │ └── rasterio.py ├── logging │ ├── __init__.py │ ├── _loggerholder.py │ ├── config.py │ ├── ilogger.py │ ├── logging_decorators.py │ ├── loglevel.py │ ├── logurulogger.py │ ├── nulllogger.py │ └── pythonlogger.py ├── mf6 │ ├── __init__.py │ ├── adv.py │ ├── api_package.py │ ├── auxiliary_variables.py │ ├── boundary_condition.py │ ├── buy.py │ ├── chd.py │ ├── clipped_boundary_condition_creator.py │ ├── cnc.py │ ├── dis.py │ ├── disv.py │ ├── drn.py │ ├── dsp.py │ ├── evt.py │ ├── exchangebase.py │ ├── ghb.py │ ├── gwfgwf.py │ ├── gwfgwt.py │ ├── gwtgwt.py │ ├── hfb.py │ ├── ic.py │ ├── ims.py │ ├── ist.py │ ├── lak.py │ ├── mf6_hfb_adapter.py │ ├── mf6_wel_adapter.py │ ├── model.py │ ├── model_gwf.py │ ├── model_gwt.py │ ├── mst.py │ ├── multimodel │ │ ├── exchange_creator.py │ │ ├── exchange_creator_structured.py │ │ ├── exchange_creator_unstructured.py │ │ ├── modelsplitter.py │ │ └── partition_generator.py │ ├── npf.py │ ├── oc.py │ ├── out │ │ ├── __init__.py │ │ ├── cbc.py │ │ ├── common.py │ │ ├── dis.py │ │ ├── disu.py │ │ └── disv.py │ ├── package.py │ ├── pkgbase.py │ ├── rch.py │ ├── regrid │ │ ├── __init__.py │ │ └── regrid_schemes.py │ ├── riv.py │ ├── simulation.py │ ├── src.py │ ├── ssm.py │ ├── sto.py │ ├── timedis.py │ ├── utilities │ │ ├── __init__.py │ │ ├── chd_concat.py │ │ ├── dataset.py │ │ ├── imod5_converter.py │ │ ├── mf6hfb.py │ │ └── package.py │ ├── uzf.py │ ├── validation.py │ ├── validation_context.py │ ├── wel.py │ └── write_context.py ├── msw │ ├── __init__.py │ ├── copy_files.py │ ├── coupler_mapping.py │ ├── fixed_format.py │ ├── grid_data.py │ ├── idf_mapping.py │ ├── infiltration.py │ ├── initial_conditions.py │ ├── landuse.py │ ├── meteo_grid.py │ ├── meteo_mapping.py │ ├── model.py │ ├── output_control.py │ ├── pkgbase.py │ ├── ponding.py │ ├── regrid │ │ └── regrid_schemes.py │ ├── scaling_factors.py │ ├── sprinkling.py │ ├── timeutil.py │ ├── utilities │ │ ├── common.py │ │ ├── imod5_converter.py │ │ ├── mask.py │ │ └── parse.py │ └── vegetation.py ├── prepare │ ├── __init__.py │ ├── cleanup.py │ ├── common.py │ ├── hfb.py │ ├── interpolate.py │ ├── laplace.py │ ├── layer.py │ ├── layerregrid.py │ ├── partition.py │ ├── regrid.py │ ├── reproject.py │ ├── spatial.py │ ├── subsoil.py │ ├── surface_water.py │ ├── topsystem │ │ ├── __init__.py │ │ ├── allocation.py │ │ ├── conductance.py │ │ ├── default_allocation_methods.py │ │ └── resistance.py │ ├── voxelize.py │ └── wells.py ├── schemata.py ├── select │ ├── __init__.py │ ├── cross_sections.py │ ├── grid.py │ ├── layers.py │ └── points.py ├── templates │ ├── generate_mf6_templates.py │ ├── mf6 │ │ ├── api.j2 │ │ ├── exg-gwfgwf.j2 │ │ ├── exg-gwfgwt.j2 │ │ ├── exg-gwtgwt.j2 │ │ ├── gwf-buy.j2 │ │ ├── gwf-chd.j2 │ │ ├── gwf-cnc.j2 │ │ ├── gwf-dis.j2 │ │ ├── gwf-disu.j2 │ │ ├── gwf-disv.j2 │ │ ├── gwf-drn.j2 │ │ ├── gwf-evt.j2 │ │ ├── gwf-evta.j2 │ │ ├── gwf-ghb.j2 │ │ ├── gwf-gnc.j2 │ │ ├── gwf-hfb.j2 │ │ ├── gwf-ic.j2 │ │ ├── gwf-lak.j2 │ │ ├── gwf-laketable.j2 │ │ ├── gwf-maw.j2 │ │ ├── gwf-mvr.j2 │ │ ├── gwf-nam.j2 │ │ ├── gwf-npf.j2 │ │ ├── gwf-oc.j2 │ │ ├── gwf-rch.j2 │ │ ├── gwf-rcha.j2 │ │ ├── gwf-riv.j2 │ │ ├── gwf-sfr.j2 │ │ ├── gwf-sto.j2 │ │ ├── gwf-uzf.j2 │ │ ├── gwf-wel.j2 │ │ ├── gwt-adv.j2 │ │ ├── gwt-dsp.j2 │ │ ├── gwt-ist.j2 │ │ ├── gwt-mst.j2 │ │ ├── gwt-nam.j2 │ │ ├── gwt-src.j2 │ │ ├── gwt-ssm.j2 │ │ ├── sim-nam.j2 │ │ ├── sim-tdis.j2 │ │ ├── sln-ims.j2 │ │ ├── utl-lak-tab.j2 │ │ ├── utl-obs.j2 │ │ ├── utl-tas.j2 │ │ └── utl-ts.j2 │ ├── runfile.j2 │ └── seawat_runfile.j2 ├── testing.py ├── tests │ ├── .coveragerc │ ├── __init__.py │ ├── additional_code_checks.py │ ├── conftest.py │ ├── fixtures │ │ ├── __init__.py │ │ ├── backward_compatibility_fixture.py │ │ ├── flow_basic_fixture.py │ │ ├── flow_basic_unstructured_fixture.py │ │ ├── flow_transport_simulation_fixture.py │ │ ├── imod5_cap_data.py │ │ ├── imod5_well_data.py │ │ ├── mf6_circle_fixture.py │ │ ├── mf6_flow_with_transport_fixture.py │ │ ├── mf6_lake_package_fixture.py │ │ ├── mf6_modelrun_fixture.py │ │ ├── mf6_rectangle_with_lakes.py │ │ ├── mf6_small_models_fixture.py │ │ ├── mf6_twri_disv_fixture.py │ │ ├── mf6_twri_fixture.py │ │ ├── mf6_welltest_fixture.py │ │ ├── msw_fixture.py │ │ ├── msw_imod5_cap_fixture.py │ │ ├── msw_meteo_fixture.py │ │ ├── msw_model_fixture.py │ │ ├── msw_regrid_fixture.py │ │ └── package_instance_creation.py │ ├── test_benchmark.py │ ├── test_code_checks.py │ ├── test_common │ │ └── test_utilities │ │ │ ├── test_clip.py │ │ │ └── test_mask_util.py │ ├── test_couplers │ │ └── test_metamod.py │ ├── test_data.py │ ├── test_evaluate │ │ ├── test_boundaries.py │ │ ├── test_budget.py │ │ ├── test_constraints.py │ │ ├── test_cross_sections.py │ │ └── test_head.py │ ├── test_examples.py │ ├── test_formats │ │ ├── test_array_io.py │ │ ├── test_gen.py │ │ ├── test_idf.py │ │ ├── test_ipf.py │ │ ├── test_prj.py │ │ ├── test_prj_wel.py │ │ └── test_rasterio.py │ ├── test_interpolate.py │ ├── test_layer.py │ ├── test_layerregrid.py │ ├── test_logging │ │ └── test_logging.py │ ├── test_mf6 │ │ ├── test_circle.py │ │ ├── test_ex01_twri.py │ │ ├── test_ex01_twri_disv.py │ │ ├── test_ex32_periodicbc.py │ │ ├── test_exchangebase.py │ │ ├── test_import_prj.py │ │ ├── test_ist.py │ │ ├── test_mf6_LHM.py │ │ ├── test_mf6_absolute_paths.py │ │ ├── test_mf6_adv.py │ │ ├── test_mf6_api_package.py │ │ ├── test_mf6_array_masking.py │ │ ├── test_mf6_buoy.py │ │ ├── test_mf6_chd.py │ │ ├── test_mf6_clipped_boundary_condition_creator.py │ │ ├── test_mf6_cnc.py │ │ ├── test_mf6_dis.py │ │ ├── test_mf6_disv.py │ │ ├── test_mf6_drn.py │ │ ├── test_mf6_dsp.py │ │ ├── test_mf6_evt.py │ │ ├── test_mf6_flopy_compatibility.py │ │ ├── test_mf6_generalheadboundary.py │ │ ├── test_mf6_gwfgwf.py │ │ ├── test_mf6_hfb.py │ │ ├── test_mf6_hfb_adapter.py │ │ ├── test_mf6_ic.py │ │ ├── test_mf6_ims.py │ │ ├── test_mf6_lak.py │ │ ├── test_mf6_lake_table.py │ │ ├── test_mf6_lake_validation.py │ │ ├── test_mf6_logging.py │ │ ├── test_mf6_mask_simulation.py │ │ ├── test_mf6_model.py │ │ ├── test_mf6_model_masking.py │ │ ├── test_mf6_mst.py │ │ ├── test_mf6_npf.py │ │ ├── test_mf6_oc.py │ │ ├── test_mf6_out.py │ │ ├── test_mf6_pkgbase.py │ │ ├── test_mf6_rch.py │ │ ├── test_mf6_regrid_model.py │ │ ├── test_mf6_regrid_package.py │ │ ├── test_mf6_regrid_scheme.py │ │ ├── test_mf6_regrid_simulation.py │ │ ├── test_mf6_regrid_transport.py │ │ ├── test_mf6_riv.py │ │ ├── test_mf6_simulation.py │ │ ├── test_mf6_src.py │ │ ├── test_mf6_ssm.py │ │ ├── test_mf6_statusinfo.py │ │ ├── test_mf6_sto.py │ │ ├── test_mf6_timedis.py │ │ ├── test_mf6_transport_model.py │ │ ├── test_mf6_unsupported_grid_operations.py │ │ ├── test_mf6_uzf.py │ │ ├── test_mf6_uzf_model.py │ │ ├── test_mf6_wel.py │ │ ├── test_mf6_wel_lowlvl.py │ │ ├── test_multimodel │ │ │ ├── test_exchange_creator_structured.py │ │ │ ├── test_exchange_creator_unstructured.py │ │ │ ├── test_mf6_modelsplitter.py │ │ │ ├── test_mf6_modelsplitter_transport.py │ │ │ ├── test_mf6_partition_generation.py │ │ │ ├── test_mf6_partitioned_simulation_postprocessing.py │ │ │ ├── test_mf6_partitioning_structured.py │ │ │ ├── test_mf6_partitioning_unstructured.py │ │ │ └── test_mf6_partitioning_unstructured_discharge.py │ │ ├── test_package_sanity.py │ │ ├── test_schemata.py │ │ ├── test_utilities │ │ │ ├── test_grid.py │ │ │ ├── test_imod5_converter.py │ │ │ ├── test_mf6hfb.py │ │ │ ├── test_regrid_utils.py │ │ │ ├── test_resampling.py │ │ │ └── test_schemata_utilities.py │ │ └── test_well_highlvl.py │ ├── test_msw │ │ ├── test_annual_crop_factors.py │ │ ├── test_copy_files.py │ │ ├── test_coupler_mapping.py │ │ ├── test_evapotranspiration_mapping.py │ │ ├── test_grid_data.py │ │ ├── test_idf_mapping.py │ │ ├── test_infiltration.py │ │ ├── test_initial_conditions.py │ │ ├── test_landuse_options.py │ │ ├── test_meteo_grid.py │ │ ├── test_model.py │ │ ├── test_output_control.py │ │ ├── test_ponding.py │ │ ├── test_precipitation_mapping.py │ │ ├── test_scaling_factors.py │ │ ├── test_sprinkling.py │ │ └── test_utilities │ │ │ ├── test_mask.py │ │ │ └── test_parse.py │ ├── test_prepare │ │ ├── test_assign_wells.py │ │ ├── test_cleanup.py │ │ ├── test_common.py │ │ ├── test_prepare_hfb.py │ │ ├── test_topsystem.py │ │ └── test_topsystem_cases.py │ ├── test_regrid.py │ ├── test_reproject.py │ ├── test_select │ │ ├── test_select_cross_sections.py │ │ ├── test_select_grid.py │ │ ├── test_select_layers.py │ │ └── test_select_points.py │ ├── test_spatial.py │ ├── test_typing │ │ └── test_typing_grid.py │ ├── test_util │ │ ├── test_util.py │ │ ├── test_util_context.py │ │ ├── test_util_dims.py │ │ ├── test_util_hfb.py │ │ ├── test_util_path.py │ │ ├── test_util_spatial.py │ │ ├── test_util_structured.py │ │ └── test_util_time.py │ ├── test_visualize │ │ ├── test_visualize_common.py │ │ ├── test_visualize_cross_sections.py │ │ ├── test_visualize_pyvista.py │ │ ├── test_visualize_spatial.py │ │ └── test_visualize_waterbalance.py │ ├── test_voxelize.py │ └── test_wq │ │ ├── test_wq_adv.py │ │ ├── test_wq_bas.py │ │ ├── test_wq_btn.py │ │ ├── test_wq_chd.py │ │ ├── test_wq_dis.py │ │ ├── test_wq_drn.py │ │ ├── test_wq_dsp.py │ │ ├── test_wq_evt.py │ │ ├── test_wq_ghb.py │ │ ├── test_wq_lpf.py │ │ ├── test_wq_mal.py │ │ ├── test_wq_model.py │ │ ├── test_wq_oc.py │ │ ├── test_wq_pkggroup.py │ │ ├── test_wq_rch.py │ │ ├── test_wq_riv.py │ │ ├── test_wq_slv.py │ │ ├── test_wq_tvc.py │ │ ├── test_wq_vdf.py │ │ └── test_wq_wel.py ├── typing │ ├── __init__.py │ ├── grid.py │ └── structured.py ├── util │ ├── __init__.py │ ├── context.py │ ├── dims.py │ ├── expand_repetitions.py │ ├── imports.py │ ├── nested_dict.py │ ├── path.py │ ├── regrid.py │ ├── spatial.py │ ├── structured.py │ └── time.py ├── visualize │ ├── __init__.py │ ├── common.py │ ├── cross_sections.py │ ├── pyvista.py │ ├── spatial.py │ └── waterbalance.py └── wq │ ├── __init__.py │ ├── adv.py │ ├── bas.py │ ├── btn.py │ ├── chd.py │ ├── dis.py │ ├── drn.py │ ├── dsp.py │ ├── evt.py │ ├── ghb.py │ ├── lpf.py │ ├── mal.py │ ├── model.py │ ├── oc.py │ ├── pkgbase.py │ ├── pkggroup.py │ ├── rch.py │ ├── riv.py │ ├── slv.py │ ├── tvc.py │ ├── vdf.py │ └── wel.py ├── pixi.lock ├── pixi.toml └── pyproject.toml /.gitattributes: -------------------------------------------------------------------------------- 1 | # GitHub syntax highlighting 2 | pixi.lock linguist-language=YAML 3 | 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[Bug] - " 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Bug description** 11 | A clear and concise description of what the bug is. 12 | 13 | **Steps to reproduce** 14 | 1. What error message do you get? 15 | 2. Can you create a minimum reproducible example? 16 | - Can you create a small script that reproduces the error? 17 | - OR if you have a complex model: Clip a small part out of it which 18 | reproduces the error, see [the ``clip_box`` 19 | method](https://deltares.github.io/imod-python/api/generated/mf6/imod.mf6.Modflow6Simulation.clip_box.html). 20 | Then dump the clipped model [with the dump 21 | method](https://deltares.github.io/imod-python/api/generated/mf6/imod.mf6.Modflow6Simulation.dump.html). 22 | You can attach these files to the issue. 23 | 24 | **Desktop (please complete the following information):** 25 | - OS: [e.g. Windows 11] 26 | - iMOD Python Version [e.g. 0.18.0] 27 | 28 | **Additional context** 29 | Add any other context about the problem here. The more information you can 30 | provide us, the quicker we respond and fix the problem. 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[FEATURE] - " 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. For example: "I'm always frustrated when [...]" 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Fixes # 2 | 3 | # Description 4 | 10 | 11 | # Checklist 12 | 15 | 16 | - [ ] Links to correct issue 17 | - [ ] Update changelog, if changes affect users 18 | - [ ] PR title starts with ``Issue #nr``, e.g. ``Issue #737`` 19 | - [ ] Unit tests were added 20 | - [ ] **If feature added**: Added/extended example 21 | -------------------------------------------------------------------------------- /.sonarcloud.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=Deltares_imod-python 2 | sonar.organization=deltares 3 | sonar.exclusions=examples/**/*, imod/tests/**/* 4 | sonar.python.version=3.10, 3.11, 3.12, 3.13 5 | -------------------------------------------------------------------------------- /.sonarlint/connectedMode.json: -------------------------------------------------------------------------------- 1 | { 2 | "sonarCloudOrganization": "deltares", 3 | "projectKey": "Deltares_imod-python", 4 | "region": "EU" 5 | } -------------------------------------------------------------------------------- /.teamcity/Dockerfile/Dockerfile: -------------------------------------------------------------------------------- 1 | # escape=` 2 | 3 | FROM mcr.microsoft.com/windows/server:ltsc2022 4 | LABEL maintainer="sunny.titus@deltares.nl" 5 | 6 | ARG PIXI_VERSION=v0.39.2 7 | 8 | ## Setup user 9 | USER "NT Authority\System" 10 | 11 | # Install .NET 4.8 12 | ADD https://download.visualstudio.microsoft.com/download/pr/2d6bb6b2-226a-4baa-bdec-798822606ff1/8494001c276a4b96804cde7829c04d7f/ndp48-x86-x64-allos-enu.exe /ndp48-x86-x64-allos-enu.exe 13 | RUN C:\ndp48-x86-x64-allos-enu.exe /quiet /install && del C:\ndp48-x86-x64-allos-enu.exe 14 | 15 | ## Install chocolatey 16 | ENV ChocolateyUseWindowsCompression false 17 | RUN powershell Set-ExecutionPolicy Bypass -Scope Process -Force;` 18 | [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072;` 19 | iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) 20 | 21 | ## Install useful packages 22 | RUN choco install -y --no-progress ` 23 | git.install ` 24 | powershell-core 25 | 26 | ## Install Pixi 27 | RUN ["powershell", "iwr -useb https://pixi.sh/install.ps1 | iex"] 28 | 29 | CMD [ "cmd" ] -------------------------------------------------------------------------------- /.teamcity/Templates/ExamplesTemplate.kt: -------------------------------------------------------------------------------- 1 | package Templates 2 | 3 | import jetbrains.buildServer.configs.kotlin.AbsoluteId 4 | import jetbrains.buildServer.configs.kotlin.DslContext 5 | import jetbrains.buildServer.configs.kotlin.Template 6 | import jetbrains.buildServer.configs.kotlin.buildFeatures.XmlReport 7 | import jetbrains.buildServer.configs.kotlin.buildFeatures.dockerSupport 8 | import jetbrains.buildServer.configs.kotlin.buildFeatures.xmlReport 9 | import jetbrains.buildServer.configs.kotlin.buildSteps.ScriptBuildStep 10 | import jetbrains.buildServer.configs.kotlin.buildSteps.script 11 | 12 | object ExamplesTemplate : Template({ 13 | name = "ExamplesTemplate" 14 | 15 | artifactRules = """imod-python\imod\tests\temp => test_output.zip""" 16 | 17 | vcs { 18 | root(DslContext.settingsRoot, "+:. => imod-python") 19 | root(AbsoluteId("iMOD6_IMODPython_MetaSwapLookupTable"), ". => lookup_table") 20 | 21 | cleanCheckout = true 22 | } 23 | 24 | steps { 25 | script { 26 | name = "Run examples" 27 | id = "Run_examples" 28 | workingDir = "imod-python" 29 | scriptContent = """ 30 | SET PATH=%%PATH%%;%system.teamcity.build.checkoutDir%\modflow6 31 | pixi run --environment default --frozen examples 32 | """.trimIndent() 33 | formatStderrAsError = true 34 | dockerImage = "%DockerContainer%:%DockerVersion%" 35 | dockerImagePlatform = ScriptBuildStep.ImagePlatform.Windows 36 | dockerRunParameters = """--cpus=8 --memory=32g""" 37 | dockerPull = false 38 | } 39 | } 40 | 41 | features { 42 | dockerSupport { 43 | loginToRegistry = on { 44 | dockerRegistryId = "PROJECT_EXT_134" 45 | } 46 | } 47 | xmlReport { 48 | reportType = XmlReport.XmlReportType.JUNIT 49 | rules = "imod-python/imod/tests/*report.xml" 50 | } 51 | } 52 | }) -------------------------------------------------------------------------------- /.teamcity/Templates/GitHubIntegrationTemplate.kt: -------------------------------------------------------------------------------- 1 | package Templates 2 | 3 | import jetbrains.buildServer.configs.kotlin.DslContext 4 | import jetbrains.buildServer.configs.kotlin.Template 5 | import jetbrains.buildServer.configs.kotlin.buildFeatures.PullRequests 6 | import jetbrains.buildServer.configs.kotlin.buildFeatures.commitStatusPublisher 7 | import jetbrains.buildServer.configs.kotlin.buildFeatures.pullRequests 8 | 9 | object GitHubIntegrationTemplate : Template({ 10 | name = "GitHubIntegrationTemplate" 11 | 12 | features { 13 | commitStatusPublisher { 14 | vcsRootExtId = "${DslContext.settingsRoot.id}" 15 | publisher = github { 16 | githubUrl = "https://api.github.com" 17 | authType = personalToken { 18 | token = "credentialsJSON:558df52e-822f-4d9d-825a-854846a9a2ff" 19 | } 20 | } 21 | } 22 | pullRequests { 23 | vcsRootExtId = "${DslContext.settingsRoot.id}" 24 | provider = github { 25 | authType = token { 26 | token = "credentialsJSON:558df52e-822f-4d9d-825a-854846a9a2ff" 27 | } 28 | filterAuthorRole = PullRequests.GitHubRoleFilter.MEMBER 29 | } 30 | } 31 | } 32 | }) -------------------------------------------------------------------------------- /.teamcity/Templates/LintTemplate.kt: -------------------------------------------------------------------------------- 1 | package Templates 2 | 3 | import jetbrains.buildServer.configs.kotlin.DslContext 4 | import jetbrains.buildServer.configs.kotlin.Template 5 | import jetbrains.buildServer.configs.kotlin.buildFeatures.dockerSupport 6 | import jetbrains.buildServer.configs.kotlin.buildSteps.ScriptBuildStep 7 | import jetbrains.buildServer.configs.kotlin.buildSteps.script 8 | 9 | object LintTemplate : Template({ 10 | name = "LintTemplate" 11 | 12 | detectHangingBuilds = false 13 | 14 | vcs { 15 | root(DslContext.settingsRoot, "+:. => imod-python") 16 | 17 | cleanCheckout = true 18 | } 19 | 20 | steps { 21 | script { 22 | name = "Static code analysis" 23 | id = "Static_code_analysis" 24 | workingDir = "imod-python" 25 | scriptContent = """ 26 | pixi run --environment default --frozen lint 27 | """.trimIndent() 28 | formatStderrAsError = true 29 | dockerImage = "%DockerContainer%:%DockerVersion%" 30 | dockerImagePlatform = ScriptBuildStep.ImagePlatform.Windows 31 | dockerRunParameters = """--cpus=4 --memory=16g""" 32 | dockerPull = false 33 | } 34 | } 35 | 36 | features { 37 | dockerSupport { 38 | loginToRegistry = on { 39 | dockerRegistryId = "PROJECT_EXT_134" 40 | } 41 | } 42 | } 43 | }) -------------------------------------------------------------------------------- /.teamcity/Templates/PipPythonTemplate.kt: -------------------------------------------------------------------------------- 1 | package Templates 2 | 3 | import jetbrains.buildServer.configs.kotlin.DslContext 4 | import jetbrains.buildServer.configs.kotlin.Template 5 | import jetbrains.buildServer.configs.kotlin.buildFeatures.dockerSupport 6 | import jetbrains.buildServer.configs.kotlin.buildSteps.ScriptBuildStep 7 | import jetbrains.buildServer.configs.kotlin.buildSteps.script 8 | import jetbrains.buildServer.configs.kotlin.matrix 9 | 10 | object PipPythonTemplate : Template({ 11 | name = "PipPythonTemplate" 12 | 13 | detectHangingBuilds = false 14 | 15 | vcs { 16 | root(DslContext.settingsRoot, "+:. => imod-python") 17 | 18 | cleanCheckout = true 19 | } 20 | 21 | steps { 22 | script { 23 | name = "Pip install python" 24 | id = "pip_install" 25 | workingDir = "imod-python" 26 | scriptContent = """ 27 | pixi run --environment %python_env% --frozen test_import 28 | """.trimIndent() 29 | formatStderrAsError = true 30 | dockerImage = "%DockerContainer%:%DockerVersion%" 31 | dockerImagePlatform = ScriptBuildStep.ImagePlatform.Windows 32 | dockerRunParameters = """--cpus=4 --memory=16g""" 33 | dockerPull = false 34 | } 35 | } 36 | 37 | features { 38 | dockerSupport { 39 | loginToRegistry = on { 40 | dockerRegistryId = "PROJECT_EXT_134" 41 | } 42 | } 43 | matrix { 44 | param( 45 | "python_env", listOf( 46 | value("py310", label = "python 3.10"), 47 | value("py311", label = "python 3.11"), 48 | value("py312", label = "python 3.12"), 49 | value("py313", label = "python 3.13") 50 | ) 51 | ) 52 | } 53 | } 54 | }) 55 | -------------------------------------------------------------------------------- /.teamcity/settings.kts: -------------------------------------------------------------------------------- 1 | import jetbrains.buildServer.configs.kotlin.project 2 | import jetbrains.buildServer.configs.kotlin.version 3 | 4 | /* 5 | The settings script is an entry point for defining a single 6 | TeamCity project. TeamCity looks for the 'settings.kts' file in a 7 | project directory and runs it if it's found, so the script name 8 | shouldn't be changed and its package should be the same as the 9 | project's id. 10 | 11 | The script should contain a single call to the project() function 12 | with a Project instance or an init function as an argument. 13 | 14 | VcsRoots, BuildTypes, and Templates of this project must be 15 | registered inside project using the vcsRoot(), buildType(), and 16 | template() methods respectively. 17 | 18 | Subprojects can be defined either in their own settings.kts or by 19 | calling the subProjects() method in this project. 20 | 21 | To debug settings scripts in command-line, run the 22 | 23 | mvnDebug org.jetbrains.teamcity:teamcity-configs-maven-plugin:generate 24 | 25 | command and attach your debugger to the port 8000. 26 | 27 | To debug in IntelliJ Idea, open the 'Maven Projects' tool window (View -> 28 | Tool Windows -> Maven Projects), find the generate task 29 | node (Plugins -> teamcity-configs -> teamcity-configs:generate), 30 | the 'Debug' option is available in the context menu for the task. 31 | */ 32 | 33 | version = "2024.03" 34 | project(_Self.MainProject) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Deltares 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = imod 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/_static/deltares-white.svg: -------------------------------------------------------------------------------- 1 | Artboard 1 -------------------------------------------------------------------------------- /docs/_static/deltares.js: -------------------------------------------------------------------------------- 1 | FontAwesome.library.add( 2 | (faListOldStyle = { 3 | prefix: "fa-custom", 4 | iconName: "deltares", 5 | icon: [ 6 | 400, // viewBox width 7 | 130, // viewBox height 8 | [], // ligature 9 | "e001", // unicode codepoint - private use area 10 | "M86.5,63.9c0-22.4-12.6-31.6-33-31.6-6.4,0-12.6,0-15.8.2s-5.2.6-5.2,3.8V97.5a177.7,177.7,0,0,0,18.2,1c20.8,0,35.8-12.2,35.8-34.6Zm-13.4,1c0,14.4-8.8,23-21.2,23a42.74,42.74,0,0,1-6.8-.5V43s3.6-.3,7.6-.3c14.4,0,20.4,7.8,20.4,22.2Zm20.2,9c0,16.4,8,24.6,24.4,24.6a43.47,43.47,0,0,0,15.2-2.6V86.5a43.59,43.59,0,0,1-13.6,2.4c-8.8,0-13.6-4.6-13.6-13.4h24.6c4,0,5.6-1,5.6-3.8V69.9c0-12-6.4-21.4-20-21.4-14.4,0-22.6,11-22.6,25.4Zm22.2-16.2c5.2,0,8,3.6,8,9.6H105.9c.8-6,4.4-9.6,9.6-9.6Zm42-25.2h-6.8c-4,0-5.6,1-5.6,3.8V82.7c0,11.2,3.4,15.8,12.6,15.8a28.39,28.39,0,0,0,8.2-1.3V88.1a18.68,18.68,0,0,1-4.2.6c-2.5,0-4.2-1.2-4.2-6.4Zm28.4,0h-6.6c-4,0-5.6,1-5.6,3.8V82.7c0,11.2,3.6,15.8,14,15.8a36.59,36.59,0,0,0,9.8-1.3V87.7a26.19,26.19,0,0,1-5.8.8c-3.6,0-5.8-1.8-5.8-7.8V59.1h6c4,0,5.6-1,5.6-3.8V49.5H185.9Zm17.6,43.4c0,16.4,10.4,22.6,23.2,22.6,8,0,16.2-1.6,21-3.6V51.1a62.22,62.22,0,0,0-17.2-2.6c-16.8,0-27,11-27,27.4Zm12.8-1.2c0-11.6,5.8-16.6,13-16.6a26.3,26.3,0,0,1,6.2.7V87.7s-2.6,1.2-7.8,1.2c-8,0-11.4-5.4-11.4-14.2Zm44,22.8h12.4V68.3c0-6.8,1.8-9.2,6.6-9.2h.6c3.6,0,5.4-.6,5.4-3.8V49.5h-7.6c-13.6,0-17.4,8.4-17.4,18.8Zm27.8-23.6c0,16.4,8,24.6,24.4,24.6a43.47,43.47,0,0,0,15.2-2.6V86.5a43.59,43.59,0,0,1-13.6,2.4c-8.8,0-13.6-4.6-13.6-13.4h24.6c4,0,5.6-1,5.6-3.8V69.9c0-12-6.4-21.4-20-21.4-14.4,0-22.6,11-22.6,25.4Zm22.2-16.2c5.2,0,8,3.6,8,9.6H300.7c.8-6,4.4-9.6,9.6-9.6Zm28,6.2c0,16,19.5,11.6,19.5,20,0,3.2-3.1,5-7.9,5a40.07,40.07,0,0,1-10.3-1.4l-.2,10a48.7,48.7,0,0,0,9.9,1c12.8,0,21.3-6.2,21.3-15.8,0-15.6-19.7-12.4-19.7-20,0-3.2,2.8-4.8,7.2-4.8,1.6,0,3.2.1,4.4.1,2.8,0,5-.5,5-5.3V49.3a56.4,56.4,0,0,0-8.8-.8C345.1,48.5,338.3,55.5,338.3,63.9Z", 11 | ], 12 | }), 13 | ); -------------------------------------------------------------------------------- /docs/_static/deltares.svg: -------------------------------------------------------------------------------- 1 | Artboard 1 -------------------------------------------------------------------------------- /docs/_templates/custom-class-template.rst: -------------------------------------------------------------------------------- 1 | {{ fullname | escape | underline}} 2 | 3 | .. currentmodule:: {{ module }} 4 | 5 | .. autoclass:: {{ objname }} 6 | :members: 7 | :show-inheritance: 8 | :inherited-members: 9 | 10 | {% block methods %} 11 | .. automethod:: __init__ 12 | 13 | {% if methods %} 14 | .. rubric:: {{ _('Methods') }} 15 | 16 | .. autosummary:: 17 | {% for item in methods %} 18 | ~{{ name }}.{{ item }} 19 | {%- endfor %} 20 | {% endif %} 21 | {% endblock %} 22 | 23 | {% block attributes %} 24 | {% if attributes %} 25 | .. rubric:: {{ _('Attributes') }} 26 | 27 | .. autosummary:: 28 | {% for item in attributes %} 29 | ~{{ name }}.{{ item }} 30 | {%- endfor %} 31 | {% endif %} 32 | {% endblock %} -------------------------------------------------------------------------------- /docs/_templates/custom-module-template.rst: -------------------------------------------------------------------------------- 1 | {{ fullname | escape | underline}} 2 | 3 | .. automodule:: {{ fullname }} 4 | 5 | {% block attributes %} 6 | {% if attributes %} 7 | .. rubric:: Module Attributes 8 | 9 | .. autosummary:: 10 | :toctree: 11 | {% for item in attributes %} 12 | {{ item }} 13 | {%- endfor %} 14 | {% endif %} 15 | {% endblock %} 16 | 17 | {% block functions %} 18 | {% if functions %} 19 | .. rubric:: {{ _('Functions') }} 20 | 21 | .. autosummary:: 22 | :toctree: 23 | {% for item in functions %} 24 | {{ item }} 25 | {%- endfor %} 26 | {% endif %} 27 | {% endblock %} 28 | 29 | {% block classes %} 30 | {% if classes %} 31 | .. rubric:: {{ _('Classes') }} 32 | 33 | .. autosummary:: 34 | :toctree: 35 | :template: custom-class-template.rst 36 | {% for item in classes %} 37 | {{ item }} 38 | {%- endfor %} 39 | {% endif %} 40 | {% endblock %} 41 | 42 | {% block exceptions %} 43 | {% if exceptions %} 44 | .. rubric:: {{ _('Exceptions') }} 45 | 46 | .. autosummary:: 47 | :toctree: 48 | {% for item in exceptions %} 49 | {{ item }} 50 | {%- endfor %} 51 | {% endif %} 52 | {% endblock %} 53 | 54 | {% block modules %} 55 | {% if modules %} 56 | .. rubric:: Modules 57 | 58 | .. autosummary:: 59 | :toctree: 60 | :template: custom-module-template.rst 61 | :recursive: 62 | {% for item in modules %} 63 | {{ item }} 64 | {%- endfor %} 65 | {% endif %} 66 | {% endblock %} -------------------------------------------------------------------------------- /docs/api/evaluate.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: imod.evaluate 2 | 3 | Evaluate model output 4 | --------------------- 5 | 6 | .. autosummary:: 7 | :toctree: generated/evaluate 8 | 9 | calculate_gxg 10 | 11 | convert_pointwaterhead_freshwaterhead 12 | 13 | facebudget 14 | flow_velocity 15 | 16 | interpolate_value_boundaries 17 | 18 | quiver_line 19 | quiver_linestring 20 | streamfunction_line 21 | streamfunction_linestring 22 | 23 | intra_cell_boundary_conditions 24 | stability_constraint_advection 25 | stability_constraint_wel 26 | -------------------------------------------------------------------------------- /docs/api/index.rst: -------------------------------------------------------------------------------- 1 | API Reference 2 | ============= 3 | 4 | This page provides an auto-generated summary of imod's API. 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | 9 | changelog 10 | 11 | io 12 | prepare 13 | select 14 | evaluate 15 | visualize 16 | util 17 | logging 18 | 19 | mf6 20 | wq 21 | msw 22 | metamod 23 | -------------------------------------------------------------------------------- /docs/api/io.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: imod.formats 2 | 3 | Input/output 4 | ------------ 5 | 6 | .. autosummary:: 7 | :toctree: generated/io 8 | 9 | idf.open 10 | idf.open_dataset 11 | idf.open_subdomains 12 | idf.save 13 | idf.header 14 | 15 | ipf.read 16 | ipf.read_associated 17 | ipf.save 18 | ipf.write 19 | ipf.write_assoc 20 | 21 | rasterio.header 22 | rasterio.open 23 | rasterio.save 24 | 25 | gen.read 26 | gen.write 27 | gen.read_ascii 28 | 29 | prj.read_projectfile 30 | prj.open_projectfile_data 31 | prj.read_timfile 32 | -------------------------------------------------------------------------------- /docs/api/logging.rst: -------------------------------------------------------------------------------- 1 | Logging 2 | --------- 3 | 4 | .. autosummary:: 5 | :toctree: generated/logging 6 | :template: custom-module-template.rst 7 | :recursive: 8 | 9 | imod.logging -------------------------------------------------------------------------------- /docs/api/metamod.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: imod.couplers.metamod 2 | 3 | MetaMod 4 | ------- 5 | 6 | .. autosummary:: 7 | :toctree: generated/metamod 8 | 9 | MetaMod 10 | -------------------------------------------------------------------------------- /docs/api/msw.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: imod.msw 2 | 3 | MetaSWAP 4 | -------- 5 | 6 | .. autosummary:: 7 | :toctree: generated/msw 8 | 9 | GridData 10 | GridData.from_imod5_data 11 | Infiltration 12 | Infiltration.from_imod5_data 13 | Ponding 14 | Ponding.from_imod5_data 15 | ScalingFactors 16 | ScalingFactors.from_imod5_data 17 | Sprinkling 18 | Sprinkling.from_imod5_data 19 | 20 | IdfMapping 21 | TimeOutputControl 22 | VariableOutputControl 23 | 24 | InitialConditionsEquilibrium 25 | InitialConditionsPercolation 26 | InitialConditionsRootzonePressureHead 27 | InitialConditionsSavedState 28 | 29 | LanduseOptions 30 | AnnualCropFactors 31 | 32 | MeteoGrid 33 | MeteoGridCopy 34 | MeteoGridCopy.from_imod5_data 35 | EvapotranspirationMapping 36 | EvapotranspirationMapping.from_imod5_data 37 | PrecipitationMapping 38 | PrecipitationMapping.from_imod5_data 39 | 40 | CouplerMapping 41 | 42 | MetaSwapModel 43 | MetaSwapModel.write 44 | MetaSwapModel.from_imod5_data 45 | MetaSwapModel.regrid_like 46 | MetaSwapModel.clip_box 47 | -------------------------------------------------------------------------------- /docs/api/prepare.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: imod.prepare 2 | 3 | Prepare model input 4 | ------------------- 5 | 6 | .. autosummary:: 7 | :toctree: generated/prepare 8 | 9 | Regridder 10 | LayerRegridder 11 | Voxelizer 12 | 13 | fill 14 | laplace_interpolate 15 | 16 | polygonize 17 | 18 | reproject 19 | 20 | rasterize 21 | gdal_rasterize 22 | celltable 23 | rasterize_celltable 24 | 25 | zonal_aggregate_polygons 26 | zonal_aggregate_raster 27 | 28 | linestring_to_square_zpolygons 29 | linestring_to_trapezoid_zpolygons 30 | 31 | assign_wells 32 | 33 | get_lower_active_grid_cells 34 | get_lower_active_layer_number 35 | get_upper_active_grid_cells 36 | get_upper_active_layer_number 37 | create_layered_top 38 | 39 | ALLOCATION_OPTION 40 | DISTRIBUTING_OPTION 41 | SimulationAllocationOptions 42 | SimulationDistributingOptions 43 | allocate_drn_cells 44 | allocate_ghb_cells 45 | allocate_rch_cells 46 | allocate_riv_cells 47 | c_leakage 48 | c_radial 49 | distribute_drn_conductance 50 | distribute_ghb_conductance 51 | distribute_riv_conductance 52 | split_conductance_with_infiltration_factor 53 | 54 | cleanup_drn 55 | cleanup_ghb 56 | cleanup_riv 57 | cleanup_wel 58 | cleanup_wel_layered 59 | 60 | create_partition_labels -------------------------------------------------------------------------------- /docs/api/select.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: imod.select 2 | 3 | Select points and cross sections 4 | -------------------------------- 5 | 6 | .. autosummary:: 7 | :toctree: generated/select 8 | 9 | cross_section_line 10 | cross_section_linestring 11 | 12 | points_in_bounds 13 | points_values 14 | points_set_values 15 | points_indices 16 | 17 | upper_active_layer 18 | 19 | grid_boundary_xy 20 | active_grid_boundary_xy 21 | -------------------------------------------------------------------------------- /docs/api/util.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: imod.util 2 | 3 | Utilities 4 | --------- 5 | 6 | .. autosummary:: 7 | :toctree: generated/util 8 | 9 | empty_2d 10 | empty_2d_transient 11 | empty_3d 12 | empty_3d_transient 13 | 14 | where 15 | 16 | cd 17 | ignore_warnings 18 | print_if_error 19 | to_datetime 20 | 21 | coord_reference 22 | spatial_reference 23 | transform 24 | 25 | to_ugrid2d 26 | mdal_compliant_ugrid2d 27 | from_mdal_compliant_ugrid2d 28 | 29 | RegridderWeightsCache 30 | RegridderWeightsCache.get_regridder 31 | RegridderType 32 | -------------------------------------------------------------------------------- /docs/api/visualize.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: imod.visualize 2 | 3 | Visualize 4 | --------- 5 | 6 | .. autosummary:: 7 | :toctree: generated/visualize 8 | 9 | cross_section 10 | 11 | plot_map 12 | imshow_topview 13 | read_imod_legend 14 | 15 | quiver 16 | streamfunction 17 | 18 | waterbalance_barchart 19 | 20 | grid_3d 21 | line_3d 22 | GridAnimation3D 23 | StaticGridAnimation3D 24 | -------------------------------------------------------------------------------- /docs/api/wq.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: imod.wq 2 | 3 | iMOD-WQ 4 | ======= 5 | 6 | Model 7 | ----- 8 | 9 | .. autosummary:: 10 | :toctree: generated/wq 11 | 12 | SeawatModel 13 | SeawatModel.create_time_discretization 14 | 15 | Settings 16 | -------- 17 | 18 | .. autosummary:: 19 | :toctree: generated/wq 20 | 21 | TimeDiscretization 22 | OutputControl 23 | PreconditionedConjugateGradientSolver 24 | GeneralizedConjugateGradientSolver 25 | ParallelKrylovFlowSolver 26 | ParallelKrylovTransportSolver 27 | 28 | Flow 29 | ---- 30 | 31 | .. autosummary:: 32 | :toctree: generated/wq 33 | 34 | BasicFlow 35 | ConstantHead 36 | Drainage 37 | EvapotranspirationTopLayer 38 | EvapotranspirationLayers 39 | EvapotranspirationHighestActive 40 | GeneralHeadBoundary 41 | LayerPropertyFlow 42 | RechargeTopLayer 43 | RechargeLayers 44 | RechargeHighestActive 45 | River 46 | Well 47 | VariableDensityFlow 48 | 49 | Transport 50 | --------- 51 | 52 | .. autosummary:: 53 | :toctree: generated/wq 54 | 55 | AdvectionTVD 56 | AdvectionMOC 57 | AdvectionModifiedMOC 58 | AdvectionHybridMOC 59 | AdvectionFiniteDifference 60 | BasicTransport 61 | Dispersion 62 | MassLoading 63 | TimeVaryingConstantConcentration 64 | -------------------------------------------------------------------------------- /docs/clean.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | 4 | 5 | def remove_dir_content(path: str) -> None: 6 | for root, dirs, files in os.walk(path): 7 | for f in files: 8 | if f != ".gitkeep": 9 | os.unlink(os.path.join(root, f)) 10 | for d in dirs: 11 | shutil.rmtree(os.path.join(root, d)) 12 | 13 | 14 | remove_dir_content("examples/imod-wq") 15 | remove_dir_content("examples/metaswap") 16 | remove_dir_content("examples/mf6") 17 | remove_dir_content("examples/prepare") 18 | remove_dir_content("examples/visualize") 19 | 20 | remove_dir_content("user-guide") 21 | 22 | remove_dir_content("sample_data") 23 | 24 | remove_dir_content("api/generated") 25 | 26 | remove_dir_content("_build") 27 | -------------------------------------------------------------------------------- /docs/developing/documentation.rst: -------------------------------------------------------------------------------- 1 | Building documentation and examples 2 | ----------------------------------- 3 | 4 | In the ``docs`` directory, run: 5 | 6 | .. code-block:: console 7 | 8 | make html 9 | 10 | On Windows: 11 | 12 | .. code-block:: console 13 | 14 | .\make.bat html 15 | 16 | Sphinx will build the documentation in a few steps. This is generally useful, 17 | as it means only part of the documentation needs to be rebuilt after some 18 | changes. However, to start afresh, run: 19 | 20 | .. code-block:: console 21 | 22 | python clean.py 23 | 24 | This will get rid of all files generated by Sphinx. 25 | 26 | Creating the docs can also be done using pixi. Run the following command to build it: 27 | 28 | .. code-block:: console 29 | 30 | pixi run --environment default docs -------------------------------------------------------------------------------- /docs/developing/index.rst: -------------------------------------------------------------------------------- 1 | Developing 2 | ---------- 3 | 4 | We'd like this to be a community driven project, so all kinds of input 5 | are welcome! 6 | 7 | There are numerous way you could contribute: 8 | 9 | - Report bugs by submitting issues 10 | - Request features by submitting issues 11 | - Write examples and improve documentation 12 | - Contribute code: bug fixes, new features 13 | 14 | This document is loosely based on the `Contributing to xarray guide`_. 15 | It's worth reading, it covers many of the subjects below in greater 16 | detail. 17 | 18 | Reporting bugs 19 | -------------- 20 | 21 | You can report bugs on the *Issues* `pages`_. Please include a 22 | self-contained Python snippet that reproduces the problem. In the 23 | majority of cases, a Minimal, Complete, and Verifiable Example (MCVE) or 24 | Minimum Working Example (MWE) is the best way to communicate the problem 25 | and provide insight. Have a look at `this stackoverflow article`_ for an 26 | in-depth description. 27 | 28 | .. toctree:: 29 | :titlesonly: 30 | :hidden: 31 | 32 | contributing 33 | examples 34 | documentation 35 | ci 36 | releasing 37 | docker 38 | 39 | .. _Contributing to xarray guide: https://xarray.pydata.org/en/latest/contributing.html 40 | .. _pages: https://github.com/Deltares/imod-python/issues 41 | .. _this stackoverflow article: https://stackoverflow.com/help/mcve -------------------------------------------------------------------------------- /docs/examples/data/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/imod-python/ecfb70a5ff832e83eb8927a3ef93d16d92b6ce89/docs/examples/data/.gitkeep -------------------------------------------------------------------------------- /docs/examples/imod-wq/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/imod-python/ecfb70a5ff832e83eb8927a3ef93d16d92b6ce89/docs/examples/imod-wq/.gitkeep -------------------------------------------------------------------------------- /docs/examples/index.rst: -------------------------------------------------------------------------------- 1 | Examples 2 | ======== 3 | 4 | The imod package can be used in many ways. By and large, imod **enables** 5 | functionality rather than implementing it. Geopandas, pandas, and xarray 6 | provide most of the functionality; imod provides the link to the MODFLOW 7 | specific file formats. 8 | 9 | Sometimes, commonly used functionality (in groundwater modeling) is not 10 | available in existing packages, or is insufficiently fast or convenient. For 11 | these situations, imod provides a number of additional functions. These 12 | functions revolve around the same principal data structures (geopandas, pandas, 13 | xarray) and rarely introduce new data structures. 14 | 15 | The examples below follow this rationale: a great deal of demonstrated 16 | funtionality is e.g. "vanilla" xarray functionaly, but used for the goal of 17 | convenient groundwater modeling. 18 | 19 | .. toctree:: 20 | :titlesonly: 21 | :hidden: 22 | 23 | mf6/index.rst 24 | imod-wq/index.rst 25 | metaswap/index.rst 26 | prepare/index.rst 27 | visualize/index.rst 28 | -------------------------------------------------------------------------------- /docs/examples/mf6/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/imod-python/ecfb70a5ff832e83eb8927a3ef93d16d92b6ce89/docs/examples/mf6/.gitkeep -------------------------------------------------------------------------------- /docs/examples/visualize/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/imod-python/ecfb70a5ff832e83eb8927a3ef93d16d92b6ce89/docs/examples/visualize/.gitkeep -------------------------------------------------------------------------------- /docs/faq/API-design.rst: -------------------------------------------------------------------------------- 1 | API design FAQ 2 | ============== 3 | 4 | This document contains answers to common questions we get on our API design. 5 | 6 | Why does the ``"layer"`` coordinate need to start at 1 and not 0? 7 | ----------------------------------------------------------------- 8 | 9 | There is a difference between coordinate labels and indices. In Python, indices 10 | start at 0. Coordinate labels have no direct meaning to Python, so instead iMOD 11 | Python decides how coordinates should be labeled. The ``"layer"`` coordinate 12 | contains labels, just like the ``"x"`` and ``"y"`` coordinates. Because these 13 | labels are not used for indexing, we decided to keep the ``"layer"`` labels the 14 | same as the MODFLOW 6 layers. MODFLOW 6 is written in Fortran, in which the 15 | indices start at 1. 16 | -------------------------------------------------------------------------------- /docs/faq/how-do-i/in-out.rst: -------------------------------------------------------------------------------- 1 | Data In/Out 2 | ----------- 3 | 4 | Import IDF file 5 | ~~~~~~~~~~~~~~~ 6 | 7 | With :func:`imod.idf.open`: 8 | 9 | .. code-block:: python 10 | 11 | da = imod.idf.open("bottom_l1.idf") 12 | 13 | 14 | Import multiple IDF files 15 | ~~~~~~~~~~~~~~~~~~~~~~~~~ 16 | 17 | With :func:`imod.idf.open`: 18 | 19 | .. code-block:: python 20 | 21 | da = imod.idf.open("bottom_l*.idf") 22 | 23 | 24 | Import IPF file 25 | ~~~~~~~~~~~~~~~ 26 | 27 | With :func:`imod.ipf.read`: 28 | 29 | .. code-block:: python 30 | 31 | df = imod.ipf.read("timeseries.ipf") 32 | 33 | 34 | Import netCDF file as Dataset 35 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 36 | 37 | .. code-block:: python 38 | 39 | ds = xr.open_dataset("dataset.nc") 40 | 41 | Import a single netCDF variable as DataArray 42 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 43 | 44 | .. code-block:: python 45 | 46 | da = xr.open_dataarray("variable.nc") 47 | 48 | 49 | Convert structured data to UGRID netCDF 50 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 51 | 52 | With :func:`imod.util.to_ugrid2d`: 53 | 54 | .. code-block:: python 55 | 56 | ugrid_ds = imod.util.to_ugrid2d(da) 57 | ugrid_ds.to_netcdf("ds_ugrid.nc") 58 | 59 | 60 | Make data readable by QGIS 61 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 62 | 63 | This requires the data to be compliant with `GDAL 64 | `_ for structured data, or `MDAL `_ for 65 | unstructured data respectively. 66 | 67 | For structured data: 68 | 69 | .. code-block:: python 70 | 71 | da_gdal = imod.util.spatial.gdal_compliant_grid(da) 72 | da_gdal.to_netcdf("path/to/file.nc") 73 | 74 | You can open this data as **raster** data in QGIS. 75 | 76 | For unstructured data: 77 | 78 | .. code-block:: python 79 | 80 | uda_mdal = imod.util.spatial.mdal_compliant_ugrid2d(uda) 81 | uda_mdal.ugrid.to_netcdf("path/to/file.nc") 82 | 83 | You you can open this data as **mesh** data in QGIS. 84 | -------------------------------------------------------------------------------- /docs/faq/how-do-i/index.rst: -------------------------------------------------------------------------------- 1 | How do I ... 2 | ============ 3 | 4 | .. include:: in-out.rst 5 | .. include:: modification.rst 6 | .. include:: plot.rst 7 | .. include:: unstructured.rst 8 | -------------------------------------------------------------------------------- /docs/faq/how-do-i/plot.rst: -------------------------------------------------------------------------------- 1 | 2 | Plot a timeseries for a single cell 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | .. code-block:: python 6 | 7 | transient_da.sel(x=x, y=y).plot() 8 | 9 | Plot head of one layer at one time 10 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 11 | 12 | .. code-block:: python 13 | 14 | transient_da.sel(layer=1, time="2020-01-01").plot() 15 | -------------------------------------------------------------------------------- /docs/faq/index.rst: -------------------------------------------------------------------------------- 1 | FAQ 2 | === 3 | 4 | Working with MODFLOW and the imod package forces you to deal with a lot of 5 | complexity. This is inherent to working with multi-purpose hydrological models: 6 | there are hundreds of parameters, and depending on the number of cells there 7 | may be billions of parameter values to configure. Python provides an enormous 8 | number of tools to help you configure, but working with any programming lanuage 9 | brings its own complexities. Consequently, this Frequently Asked Questions 10 | (FAQ) section has been organized by subject: 11 | 12 | 13 | .. toctree:: 14 | :titlesonly: 15 | 16 | general 17 | how-do-i/index 18 | python 19 | modeling 20 | API-design 21 | known-issues 22 | imod5_backwards_compatibility 23 | -------------------------------------------------------------------------------- /docs/faq/known-issues.rst: -------------------------------------------------------------------------------- 1 | Known Issues 2 | ============ 3 | 4 | MODFLOW 6 versions 5 | ------------------ 6 | 7 | iMOD Python is tested to work with MODFLOW 6 versions: 8 | 9 | * All 6.2, 6.3 versions 10 | * 6.4.0, 6.4.2 11 | * All 6.4, and 6.5 versions 12 | 13 | Confirmed to not work: 14 | 15 | * 6.4.1 16 | 17 | 18 | iMOD5 Backwards compatibility 19 | ----------------------------- 20 | 21 | For a detailed description of known issues with iMOD5 backwards compatibility, 22 | see :doc:`imod5_backwards_compatibility`. 23 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=imod 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 20 | echo.installed, then set the SPHINXBUILD environment variable to point 21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 22 | echo.may add the Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /docs/user-guide/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/imod-python/ecfb70a5ff832e83eb8927a3ef93d16d92b6ce89/docs/user-guide/.gitkeep -------------------------------------------------------------------------------- /examples/README.rst: -------------------------------------------------------------------------------- 1 | Examples 2 | ======== 3 | 4 | The imod package can be used in many ways. By and large, imod **enables** 5 | functionality rather than implementing it: Geopandas, pandas, and xarray 6 | provide most of the functionality; imod provides the link to the MODFLOW 7 | specific file formats. 8 | 9 | Sometimes, commonly used functionality (in groundwater modeling) is not 10 | available in existing packages, or is insufficiently fast or convenient. For 11 | these situations, imod provides a number of additional functions. These 12 | functions revolve around the same principal data structures (geopandas, pandas, 13 | xarray) and rarely introduce new data structures. 14 | 15 | The examples here follow this rationale: a great deal of demonstrated 16 | funtionality is e.g. "vanilla" xarray functionality, but used for the goal of 17 | convenient groundwater modeling. -------------------------------------------------------------------------------- /examples/imod-wq/README.rst: -------------------------------------------------------------------------------- 1 | iMOD-WQ 2 | ======= 3 | 4 | These examples demonstrate how to use imod to build iMOD-WQ models. 5 | -------------------------------------------------------------------------------- /examples/metaswap/README.rst: -------------------------------------------------------------------------------- 1 | MetaSWAP 2 | ======== 3 | 4 | These examples demonstrate how to use iMOD Python to build MetaSWAP and coupled 5 | MODFLOW 6 models. -------------------------------------------------------------------------------- /examples/metaswap/metaswap_example.py: -------------------------------------------------------------------------------- 1 | """ 2 | Coupled MetaSWAP - Modflow6 model 3 | ================================= 4 | 5 | The functionality to generate coupled models has been moved to the ``primod`` package. 6 | `You can find the example here `_ 7 | """ 8 | -------------------------------------------------------------------------------- /examples/mf6/README.rst: -------------------------------------------------------------------------------- 1 | .. _mf6-introduction: 2 | 3 | MODFLOW 6 4 | ========= 5 | 6 | These examples demonstrate how to use imod to build MODFLOW 6 models. 7 | 8 | .. attention:: 9 | 10 | The examples expect you to have added the MODFLOW 6 executable to your PATH. 11 | If you have not done this yet: 12 | First, `download the latest MODFLOW 6 executables here. `_ 13 | Next, follow the following instructions to add the executable to your PATH on Windows 10: 14 | 15 | 1. Press the Start key on your keyboard. 16 | 2. Search and open “Edit the system environment variables.” 17 | 3. Go to the “Advanced” tab. 18 | 4. Click the “Environment variables” button. 19 | 5. Select the “Path” variable under “User variables” or “System variables.” 20 | 6. Click the “Edit” button. 21 | 7. Press the “New” button. 22 | 8. Type the full directory path of the program. 23 | 9. Press “Enter” to confirm the path. 24 | 10. Click “Ok.” 25 | 11. Press the “Ok” button in the Environment Variables window. 26 | 12. Click “Ok” in the System Variables window. 27 | 28 | `For more detailed instructions, see this writeup. `_ 29 | -------------------------------------------------------------------------------- /examples/mf6/circle_partitioned.py: -------------------------------------------------------------------------------- 1 | """ 2 | Circle partitioned 3 | ================== 4 | 5 | This example illustrates a circular model that is split into 3 submodels. 6 | The split method returns a simulation object that can be run as is. In this 7 | case the 3 submodels are roughly equal sized partitions that have the shape 8 | of pie pieces. 9 | """ 10 | 11 | import matplotlib.pyplot as plt 12 | from example_models import create_circle_simulation 13 | 14 | import imod 15 | from imod.prepare.partition import create_partition_labels 16 | 17 | simulation = create_circle_simulation() 18 | tmp_path = imod.util.temporary_directory() 19 | simulation.write(tmp_path / "original", False) 20 | 21 | idomain = simulation["GWF_1"]["disv"].dataset["idomain"] 22 | 23 | number_partitions = 5 24 | submodel_labels = create_partition_labels(simulation, number_partitions) 25 | 26 | # Create a simulation that is split in subdomains according to the label array. 27 | new_sim = simulation.split(submodel_labels) 28 | # %% 29 | # Write the simulation input files for the new simulation. 30 | new_sim.write(tmp_path, False) 31 | 32 | # run the split simulation 33 | new_sim.run() 34 | # %% 35 | # Visualize the computed heads in the top layer. 36 | fig, ax = plt.subplots() 37 | head = new_sim.open_head() 38 | 39 | head["head"].isel(layer=0, time=-1).ugrid.plot.contourf(ax=ax) 40 | # %% 41 | # Visualize the flow-horizontal-face-x componenty of the balances. 42 | fig, ax = plt.subplots() 43 | balances = new_sim.open_flow_budget() 44 | 45 | balances["flow-horizontal-face-x"].isel(layer=0, time=-1).ugrid.plot() 46 | pass 47 | 48 | # %% 49 | -------------------------------------------------------------------------------- /examples/prepare/README.rst: -------------------------------------------------------------------------------- 1 | Prepare 2 | ======= 3 | 4 | These examples demonstrate how to prepare you data with the iMOD Python 5 | ``prepare`` module. -------------------------------------------------------------------------------- /examples/prepare/polygonize_raster.py: -------------------------------------------------------------------------------- 1 | """ 2 | Polygonize raster 3 | ================= 4 | 5 | iMOD Python also provides convenience functions to polygonize rasters. 6 | """ 7 | 8 | import matplotlib.pyplot as plt 9 | 10 | # %% 11 | import imod 12 | 13 | # sphinx_gallery_thumbnail_number = -1 14 | 15 | # %% 16 | # We'll start off by creating an example raster ``lake_grid`` to convert to 17 | # polygons. This is similar to the `Rasterize shapefiles` example. 18 | 19 | temp_dir = imod.util.temporary_directory() 20 | lakes = imod.data.lakes_shp(temp_dir) 21 | 22 | # Create dummy grid 23 | xmin = 90950.0 24 | xmax = 115650.0 25 | dx = 200 26 | 27 | ymin = 445850.0 28 | ymax = 467550.0 29 | dy = -200.0 30 | 31 | like_2d = imod.util.empty_2d(dx, xmin, xmax, dy, ymin, ymax) 32 | 33 | # Rasterrize the shapes 34 | lake_grid = imod.prepare.rasterize(lakes, like=like_2d) 35 | 36 | # %% 37 | # Our raster looks like this: 38 | fig, ax = plt.subplots() 39 | lake_grid.plot(ax=ax) 40 | 41 | # %% 42 | # Polygonize the lakes 43 | polygonized_lakes = imod.prepare.polygonize(lake_grid) 44 | 45 | polygonized_lakes.head(5) 46 | 47 | # %% 48 | # This also polygonized the areas with np.nan. So we can drop those, using 49 | # regular pandas functionality 50 | 51 | polygonized_lakes = polygonized_lakes.dropna() 52 | 53 | polygonized_lakes.head(5) 54 | 55 | # %% 56 | # Plotted, we see a similar picture to the plotted raster 57 | fig, ax = plt.subplots() 58 | polygonized_lakes.plot(ax=ax) 59 | 60 | # %% 61 | # Since it is a GeoDataFrame, we can now do vector operations. Like computing 62 | # the centroids and plotting them as points. 63 | 64 | centroids = polygonized_lakes.centroid 65 | 66 | fig, ax = plt.subplots() 67 | polygonized_lakes.plot(ax=ax) 68 | centroids.plot(ax=ax, color="k") 69 | -------------------------------------------------------------------------------- /examples/prepare/rasterize_shp.py: -------------------------------------------------------------------------------- 1 | """ 2 | Rasterize shapefiles 3 | ====================== 4 | 5 | Importing the necessary packages: 6 | """ 7 | 8 | import matplotlib.pyplot as plt 9 | 10 | import imod 11 | 12 | # %% 13 | # Get the example shapes 14 | temp_dir = imod.util.temporary_directory() 15 | lakes = imod.data.lakes_shp(temp_dir) 16 | 17 | # %% 18 | # We'll need a dummy grid which we will use as a reference for for rasterizing 19 | # the shapefile. We are just going to create one with the convenience function. 20 | 21 | xmin = 90950.0 22 | xmax = 115650.0 23 | dx = 100 24 | 25 | ymin = 445850.0 26 | ymax = 467550.0 27 | dy = -100.0 28 | 29 | like_2d = imod.util.empty_2d(dx, xmin, xmax, dy, ymin, ymax) 30 | 31 | # %% 32 | # Rasterrize the shapes 33 | lake_grid = imod.prepare.rasterize(lakes, like=like_2d) 34 | 35 | # Plot 36 | fig, ax = plt.subplots() 37 | lake_grid.plot.imshow(ax=ax) 38 | 39 | # %% 40 | # To rasterize on a different grid, create a dummy grid 41 | 42 | dx_coarse = 200 43 | dy_coarse = -200 44 | like_2d_coarse = imod.util.empty_2d(dx_coarse, xmin, xmax, dy_coarse, ymin, ymax) 45 | 46 | lake_grid_coarse = imod.prepare.rasterize(lakes, like=like_2d_coarse) 47 | 48 | # Plot 49 | fig, ax = plt.subplots() 50 | lake_grid_coarse.plot.imshow(ax=ax) 51 | -------------------------------------------------------------------------------- /examples/user-guide/05-unstructured-grids.py: -------------------------------------------------------------------------------- 1 | """ 2 | Unstructured Grids 3 | ================== 4 | 5 | MODFLOW 6 supports unstructured grids. Unlike raster data, the connectivity of 6 | unstructured grids is irregular. This means that the number of neighboring 7 | cells is not constant; for structured grids, every cell has 4 neighbors (in 8 | 2D), or 6 neighbors (in 3D). 9 | 10 | """ 11 | 12 | # %% 13 | 14 | 15 | # %% 16 | -------------------------------------------------------------------------------- /examples/user-guide/README.rst: -------------------------------------------------------------------------------- 1 | User Guide 2 | ========== 3 | 4 | .. toctree:: 5 | :maxdepth: 1 6 | :hidden: 7 | 8 | The imod Python package builds on top of popular Python packages like 9 | `xarray`_, `pandas`_, and `geopandas`_ to prepare and analyze MODFLOW models. 10 | This user guide will introduce these packages and their data structures, and 11 | explains how they relate to the many forms of data we come across while 12 | modeling groundwater flow. 13 | 14 | This user guide is not an exhaustive explanation of these packages and their 15 | data structures. Rather this guide intends to introduce the packages, explain 16 | their roles, and how they fit together to help with groundwater modeling. 17 | 18 | The imod package provides a link between these packages and groundwater 19 | modeling.Pandas, geopandas, and xarray already provide a great deal of 20 | capabilities and features; imod expands these capabilities when they are 21 | (i)MODFLOW specific or when existing capabilities are too limited or too slow. 22 | 23 | Specifically: 24 | 25 | * input and output to (i)MODFLOW specific file formats. 26 | * data preparation, mostly "GIS-like" routines: raster to vector and vice versa, 27 | changing cell sizes, data selection, gapfilling, etc. 28 | * overview statistics, water balance, etc, 29 | * visualization of groundwater heads, cross sections, 3D animations, etc. 30 | 31 | Nearly every function in imod consumes and produces xarray, pandas, or 32 | geopandas data structures. Therefore, this guide first introduces these data 33 | structures. Secondly, it will demonstrate how a modeling workflow is set up. 34 | 35 | .. toctree:: 36 | :titlesonly: 37 | :hidden: 38 | 39 | .. _xarray: http://xarray.pydata.org 40 | .. _pandas: http://pandas.pydata.org 41 | .. _geopandas: http://geopandas.org 42 | -------------------------------------------------------------------------------- /examples/visualize/README.rst: -------------------------------------------------------------------------------- 1 | Visualization 2 | ============= 3 | 4 | imod contains a number of utilities to ease plotting of cross sections and maps 5 | with `matplotlib`_, and 3D plots with `pyvista`_. 6 | 7 | .. _matplotlib: https://matplotlib.org/ 8 | .. _pyvista: https://docs.pyvista.org/ -------------------------------------------------------------------------------- /examples/visualize/plot_map.py: -------------------------------------------------------------------------------- 1 | """ 2 | Plot maps 3 | ============= 4 | 5 | The ``imod.visualize.plot_map`` functionality of iMOD Python allows to create 6 | customized plots. 7 | 8 | """ 9 | 10 | # %% 11 | # Import the necessary packages: 12 | 13 | import numpy as np 14 | 15 | import imod 16 | 17 | # sphinx_gallery_thumbnail_number = -1 18 | 19 | # %% 20 | # Import the input data to plot: 21 | 22 | tempdir = imod.util.temporary_directory() 23 | 24 | lakes = imod.data.lakes_shp(tempdir) 25 | surface_level = imod.data.ahn()["ahn"] 26 | 27 | # %% 28 | # It is necessary to define the Matplotlib colorbar to be used and the levels 29 | # for the legend as a list. 30 | colors = "RdYlBu_r" 31 | levels = np.arange(-15, 17.5, 2.5) 32 | 33 | # %% 34 | # The next lines show the simplest way to plot the raster. 35 | imod.visualize.plot_map(surface_level, colors, levels) 36 | 37 | # %% 38 | # It is also possible to add an overlay to the previous map 39 | 40 | overlays = [{"gdf": lakes, "facecolor": "black", "alpha": 0.3}] 41 | 42 | imod.visualize.plot_map(surface_level, colors, levels, overlays=overlays) 43 | 44 | # %% 45 | # Label the colorbar as follows: 46 | imod.visualize.plot_map( 47 | surface_level, colors, levels, kwargs_colorbar={"label": "Surface level (m)"} 48 | ) 49 | 50 | # %% 51 | # And to include a basemap: 52 | import contextily as ctx 53 | 54 | src = ctx.providers.OpenStreetMap.Mapnik 55 | imod.visualize.plot_map( 56 | surface_level, 57 | colors, 58 | levels, 59 | basemap=src, 60 | kwargs_basemap={"alpha": 0.6}, 61 | overlays=overlays, 62 | kwargs_colorbar={"label": "Surface level (m)"}, 63 | ) 64 | -------------------------------------------------------------------------------- /imod/__init__.py: -------------------------------------------------------------------------------- 1 | # exports 2 | from imod import ( 3 | couplers, 4 | data, 5 | evaluate, 6 | mf6, 7 | msw, 8 | prepare, 9 | select, 10 | testing, 11 | util, 12 | visualize, 13 | wq, 14 | ) 15 | from imod.formats import gen, idf, ipf, prj, rasterio 16 | 17 | __version__ = "1.0.0rc3" 18 | -------------------------------------------------------------------------------- /imod/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/imod-python/ecfb70a5ff832e83eb8927a3ef93d16d92b6ce89/imod/common/__init__.py -------------------------------------------------------------------------------- /imod/common/interfaces/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/imod-python/ecfb70a5ff832e83eb8927a3ef93d16d92b6ce89/imod/common/interfaces/__init__.py -------------------------------------------------------------------------------- /imod/common/interfaces/idict.py: -------------------------------------------------------------------------------- 1 | import abc 2 | 3 | 4 | class IDict(abc.ABC): 5 | """ 6 | Interface for collections.UserDict 7 | """ 8 | 9 | def __setitem__(self, key, item): 10 | raise NotImplementedError 11 | 12 | def __getitem__(self, key): 13 | raise NotImplementedError 14 | 15 | def __repr__(self): 16 | raise NotImplementedError 17 | 18 | def __len__(self): 19 | raise NotImplementedError 20 | 21 | def __delitem__(self, key): 22 | raise NotImplementedError 23 | 24 | def clear(self): 25 | raise NotImplementedError 26 | 27 | def copy(self): 28 | raise NotImplementedError 29 | 30 | def has_key(self, k): 31 | raise NotImplementedError 32 | 33 | def update(self, *args, **kwargs): 34 | raise NotImplementedError 35 | 36 | def keys(self): 37 | raise NotImplementedError 38 | 39 | def values(self): 40 | raise NotImplementedError 41 | 42 | def items(self): 43 | raise NotImplementedError 44 | 45 | def pop(self, *args): 46 | raise NotImplementedError 47 | 48 | def __cmp__(self, dict_): 49 | raise NotImplementedError 50 | 51 | def __contains__(self, item): 52 | raise NotImplementedError 53 | 54 | def __iter__(self): 55 | raise NotImplementedError 56 | 57 | def __unicode__(self): 58 | raise NotImplementedError 59 | -------------------------------------------------------------------------------- /imod/common/interfaces/ilinedatapackage.py: -------------------------------------------------------------------------------- 1 | from abc import abstractmethod 2 | from typing import TYPE_CHECKING 3 | 4 | from imod.common.interfaces.ipackagebase import IPackageBase 5 | 6 | if TYPE_CHECKING: 7 | import geopandas as gpd 8 | 9 | 10 | class ILineDataPackage(IPackageBase): 11 | """ 12 | Interface for packages for which the data is defined by lines independent of the domain definition. 13 | """ 14 | 15 | @property 16 | @abstractmethod 17 | def line_data(self) -> "gpd.GeoDataFrame": 18 | raise NotImplementedError 19 | 20 | @line_data.setter 21 | @abstractmethod 22 | def line_data(self, value: "gpd.GeoDataFrame") -> None: 23 | raise NotImplementedError 24 | -------------------------------------------------------------------------------- /imod/common/interfaces/imaskingsettings.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from typing import List 3 | 4 | from imod.common.interfaces.ipackage import IPackage 5 | 6 | 7 | class IMaskingSettings(IPackage, abc.ABC): 8 | """ 9 | Interface for packages that support masking 10 | """ 11 | 12 | @property 13 | @abc.abstractmethod 14 | def skip_variables(self) -> List[str]: 15 | raise NotImplementedError 16 | -------------------------------------------------------------------------------- /imod/common/interfaces/imodel.py: -------------------------------------------------------------------------------- 1 | from abc import abstractmethod 2 | from typing import Optional, Tuple 3 | 4 | from imod.common.interfaces.idict import IDict 5 | from imod.common.statusinfo import StatusInfoBase 6 | from imod.typing import GridDataArray 7 | 8 | 9 | class IModel(IDict): 10 | """ 11 | Interface for imod.mf6.model.Modflow6Model 12 | """ 13 | 14 | @abstractmethod 15 | def mask_all_packages(self, mask: GridDataArray): 16 | raise NotImplementedError 17 | 18 | @abstractmethod 19 | def purge_empty_packages(self, model_name: Optional[str] = "") -> None: 20 | raise NotImplementedError 21 | 22 | @abstractmethod 23 | def validate(self, model_name: str = "") -> StatusInfoBase: 24 | raise NotImplementedError 25 | 26 | @property 27 | @abstractmethod 28 | def domain(self): 29 | raise NotImplementedError 30 | 31 | @property 32 | @abstractmethod 33 | def options(self) -> dict: 34 | raise NotImplementedError 35 | 36 | @property 37 | @abstractmethod 38 | def model_id(self) -> str: 39 | raise NotImplementedError 40 | 41 | @abstractmethod 42 | def is_regridding_supported(self) -> Tuple[bool, str]: 43 | raise NotImplementedError 44 | 45 | @abstractmethod 46 | def is_splitting_supported(self) -> Tuple[bool, str]: 47 | raise NotImplementedError 48 | -------------------------------------------------------------------------------- /imod/common/interfaces/ipackage.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from abc import abstractmethod 3 | from typing import Any 4 | 5 | from imod.common.interfaces.ipackagebase import IPackageBase 6 | 7 | 8 | class IPackage(IPackageBase, metaclass=abc.ABCMeta): 9 | """ 10 | Interface for imod.mf6.package.Package 11 | """ 12 | 13 | @abstractmethod 14 | def _valid(self, value): 15 | raise NotImplementedError 16 | 17 | @abstractmethod 18 | def __init__(self, *args, **kwargs) -> None: 19 | raise NotImplementedError 20 | 21 | @abc.abstractmethod 22 | def get_non_grid_data(self, grid_names: list[str]) -> dict[str, Any]: 23 | raise NotImplementedError 24 | 25 | @property 26 | @abc.abstractmethod 27 | def auxiliary_data_fields(self) -> dict[str, str]: 28 | raise NotImplementedError 29 | 30 | @abstractmethod 31 | def is_regridding_supported(self) -> bool: 32 | raise NotImplementedError 33 | -------------------------------------------------------------------------------- /imod/common/interfaces/ipackagebase.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | import xarray as xr 4 | 5 | from imod.typing import GridDataset 6 | 7 | 8 | class IPackageBase(ABC): 9 | """ 10 | Interface for imod.mf6.pkgbase.PackageBase 11 | """ 12 | 13 | @property 14 | @abstractmethod 15 | def dataset(self) -> xr.Dataset: 16 | raise NotImplementedError 17 | 18 | @dataset.setter 19 | @abstractmethod 20 | def dataset(self, value: xr.Dataset) -> None: 21 | raise NotImplementedError 22 | 23 | @classmethod 24 | @abstractmethod 25 | def _from_dataset(cls, ds: GridDataset): 26 | raise NotImplementedError 27 | -------------------------------------------------------------------------------- /imod/common/interfaces/ipointdatapackage.py: -------------------------------------------------------------------------------- 1 | from abc import abstractmethod 2 | 3 | import numpy as np 4 | from numpy.typing import NDArray 5 | 6 | from imod.common.interfaces.ipackagebase import IPackageBase 7 | 8 | 9 | class IPointDataPackage(IPackageBase): 10 | """ 11 | Interface for packages for which the data is defined by x and y coordinates independent of the domain definition. 12 | """ 13 | 14 | @property 15 | @abstractmethod 16 | def x(self) -> NDArray[np.float64]: 17 | raise NotImplementedError 18 | 19 | @property 20 | @abstractmethod 21 | def y(self) -> NDArray[np.float64]: 22 | raise NotImplementedError 23 | -------------------------------------------------------------------------------- /imod/common/interfaces/iregridpackage.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from typing import Optional 3 | 4 | from imod.common.interfaces.ipackage import IPackage 5 | from imod.common.utilities.regrid_method_type import RegridMethodType 6 | 7 | 8 | class IRegridPackage(IPackage, abc.ABC): 9 | """ 10 | Interface for packages that support regridding 11 | """ 12 | 13 | @abc.abstractmethod 14 | def get_regrid_methods(self) -> Optional[RegridMethodType]: 15 | raise NotImplementedError 16 | -------------------------------------------------------------------------------- /imod/common/interfaces/isimulation.py: -------------------------------------------------------------------------------- 1 | from abc import abstractmethod 2 | 3 | from imod.common.interfaces.idict import IDict 4 | from imod.common.interfaces.imodel import IModel 5 | 6 | 7 | class ISimulation(IDict): 8 | """ 9 | Interface for imod.mf6.simulation.Modflow6Simulation 10 | """ 11 | 12 | @abstractmethod 13 | def is_split(self) -> bool: 14 | raise NotImplementedError 15 | 16 | @abstractmethod 17 | def has_one_flow_model(self) -> bool: 18 | raise NotImplementedError 19 | 20 | @abstractmethod 21 | def get_models(self) -> dict[str, IModel]: 22 | raise NotImplementedError 23 | 24 | @abstractmethod 25 | def get_models_of_type(self, model_id: str) -> dict[str, IModel]: 26 | raise NotImplementedError 27 | -------------------------------------------------------------------------------- /imod/common/utilities/layer.py: -------------------------------------------------------------------------------- 1 | from imod.typing import GridDataArray 2 | from imod.typing.grid import zeros_like 3 | 4 | 5 | def create_layered_top(bottom: GridDataArray, top: GridDataArray) -> GridDataArray: 6 | """ 7 | Create a top array with a layer dimension, from a top array with no layer 8 | dimension and a bottom array with a layer dimension. The (output) top of 9 | layer n is assigned the bottom of layer n-1. 10 | 11 | Parameters 12 | ---------- 13 | bottom: {DataArray, UgridDataArray} 14 | Bottoms with layer dimension 15 | top: {DataArray, UgridDataArray} 16 | Top, without layer dimension 17 | 18 | Returns 19 | ------- 20 | new_top: {DataArray, UgridDataArray} 21 | Top with layer dimension. 22 | """ 23 | new_top = zeros_like(bottom) 24 | new_top[0] = top 25 | new_top[1:] = bottom[0:-1].values 26 | 27 | return new_top 28 | -------------------------------------------------------------------------------- /imod/common/utilities/regrid_method_type.py: -------------------------------------------------------------------------------- 1 | from typing import ClassVar, Protocol, Tuple, TypeAlias, runtime_checkable 2 | 3 | from pydantic import ConfigDict 4 | from pydantic.dataclasses import dataclass 5 | 6 | from imod.util.regrid import RegridderType 7 | 8 | 9 | @runtime_checkable 10 | class RegridMethodType(Protocol): 11 | # Work around that type annotation is a bit hard on dataclasses, as they 12 | # don't expose a class interface. 13 | # Adapted from: https://stackoverflow.com/a/55240861 14 | # "As already noted in comments, checking for this attribute is currently the 15 | # most reliable way to ascertain that something is a dataclass" 16 | # See also: 17 | # https://github.com/python/mypy/issues/6568#issuecomment-1324196557 18 | 19 | __dataclass_fields__: ClassVar[dict] 20 | 21 | def asdict(self) -> dict: 22 | return vars(self) 23 | 24 | 25 | _CONFIG = ConfigDict(extra="forbid") 26 | 27 | 28 | @dataclass(config=_CONFIG) 29 | class EmptyRegridMethod(RegridMethodType): 30 | pass 31 | 32 | 33 | _RegridVarType: TypeAlias = Tuple[RegridderType, str] | Tuple[RegridderType] 34 | -------------------------------------------------------------------------------- /imod/common/utilities/value_filters.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | import numpy as np 4 | import xarray as xr 5 | from xarray.core.utils import is_scalar 6 | 7 | from imod.typing import GridDataset 8 | 9 | 10 | def is_valid(value: Any) -> bool: 11 | """ 12 | Filters values that are None, False, or a numpy.bool_ False. 13 | Needs to be this specific, since 0.0 and 0 are valid values, but are 14 | equal to a boolean False. 15 | """ 16 | # Test singletons 17 | if value is False or value is None: 18 | return False 19 | # Test numpy bool (not singleton) 20 | elif isinstance(value, np.bool_) and not value: 21 | return False 22 | # When dumping to netCDF and reading back, None will have been 23 | # converted into a NaN. Only check NaN if it's a floating type to avoid 24 | # TypeErrors. 25 | elif np.issubdtype(type(value), np.floating) and np.isnan(value): 26 | return False 27 | else: 28 | return True 29 | 30 | 31 | def is_empty_dataarray(da: Any) -> bool: 32 | return isinstance(da, xr.DataArray) and da.isnull().all().item() 33 | 34 | 35 | def get_scalar_variables(ds: GridDataset) -> list[str]: 36 | """Returns scalar variables in a dataset.""" 37 | return [var for var, arr in ds.variables.items() if is_scalar(arr)] 38 | 39 | 40 | def enforce_scalar(a: np.ndarray) -> np.ndarray: 41 | """Enforce scalar value from array.""" 42 | if a.size == 1: 43 | return a.item() 44 | return ValueError(f"Array has size {a.size}, expected size 1.") 45 | -------------------------------------------------------------------------------- /imod/couplers/__init__.py: -------------------------------------------------------------------------------- 1 | from imod.couplers.metamod import MetaMod 2 | -------------------------------------------------------------------------------- /imod/couplers/metamod/__init__.py: -------------------------------------------------------------------------------- 1 | from imod.couplers.metamod.metamod import MetaMod 2 | -------------------------------------------------------------------------------- /imod/couplers/metamod/metamod.py: -------------------------------------------------------------------------------- 1 | from textwrap import dedent 2 | 3 | 4 | class MetaMod: 5 | """ 6 | This class has been moved to ``primod``, 7 | `See example here `_. 8 | """ 9 | 10 | def __init__(self, *args, **kwargs): 11 | message = dedent( 12 | """\ 13 | This class has been moved to the ``primod`` package. 14 | For an example, see: 15 | https://deltares.github.io/iMOD-Documentation/coupler_metamod_example.html 16 | """ 17 | ) 18 | raise NotImplementedError(message) 19 | -------------------------------------------------------------------------------- /imod/data/__init__.py: -------------------------------------------------------------------------------- 1 | # from .synthetic import 2 | from .sample_data import ( 3 | ahn, 4 | circle, 5 | colleagues_river_data, 6 | fluxes, 7 | head_observations, 8 | hondsrug_crosssection, 9 | hondsrug_drainage, 10 | hondsrug_initial, 11 | hondsrug_layermodel, 12 | hondsrug_layermodel_topsystem, 13 | hondsrug_meteorology, 14 | hondsrug_planar_river, 15 | hondsrug_river, 16 | hondsrug_simulation, 17 | imod5_projectfile_data, 18 | lakes_shp, 19 | tutorial_03, 20 | twri_output, 21 | ) 22 | -------------------------------------------------------------------------------- /imod/data/registry.txt: -------------------------------------------------------------------------------- 1 | ex01-twri-output.zip e229c60aa9facaca84e2189db652b5a5d2f50f8e6e6c570dd948a3f903b628fa 2 | hondsrug-initial.nc 36a9f3a75c72af1fca8ca4bc39a0c1d1449fb8ca16fcfbeebc75f0d3a6be577e 3 | hondsrug-layermodel.nc 326cb02091b7c69f705b9ce6e61ee9ab4c05c1c5389e07a57d79b4eff8eb1a88 4 | hondsrug-meteorology.nc 9fde1209099f6b129f25a07103206ce1ef0580a8c84fdb462af32af5e6f783ae 5 | hondsrug-river.nc ab8b648b9df6736362913547502bc6ac514e6231178b5e1c41c872f16873c90d 6 | hondsrug-drainage.nc ce62d22e19f4fb9a7e8d270b51008036e48402fafa79fc6b3f33cf33bd696c88 7 | hondsrug-simulation.zip da245526e967c40b991c5511e893dcf5198cf8ae73903c3d7212676fa5c006c2 8 | hondsrug-crosssection.zip 261ab2dd759730c4c8a149905d7d10fba1add17192071fdbd28a9582d8d74585 9 | head-observations.csv 011faa84d2ef55e394f32d4d1d71d60b390258de3b5980a00561ed30e0683123 10 | fluxes.nc 9a3a4d3228ea9e7d19b6300a8da762a819cadbcd99a9632c4fb511a6212e3715 11 | ahn.nc a9f172ce7dd78533f7b30693d728c74c8fd158564d4075c0dbfbb657a39fdc89 12 | lakes_shp.zip 564c20d722639f1ca19bb78e32bfe575e1e0779f3c922a1aa1a386c2a43db10d 13 | circle-nodes.txt 1ff03b8721c6e7bb6289251373d66d5b8ee2388c03493dfc75c1bf1fe7e586d2 14 | circle-triangles.txt 546d5e6ba6f9d16bb5330313edf36d4a9fc4821c9a89eedfa76ac2266dd2a5fa 15 | iMOD5_model.zip e5808579bd4f0663f0b04f8333b3e2edde785a92c83156837d29bbaed595940f 16 | iMOD-Documentation-tutorial_03.zip e4184f08c53d263939e1ead3bb66a9701e0a326ffaeda9b34a54999912a118cb -------------------------------------------------------------------------------- /imod/data/synthetic.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/imod-python/ecfb70a5ff832e83eb8927a3ef93d16d92b6ce89/imod/data/synthetic.py -------------------------------------------------------------------------------- /imod/evaluate/__init__.py: -------------------------------------------------------------------------------- 1 | from imod.evaluate.boundaries import interpolate_value_boundaries 2 | from imod.evaluate.budget import facebudget, flow_velocity 3 | from imod.evaluate.constraints import ( 4 | intra_cell_boundary_conditions, 5 | stability_constraint_advection, 6 | stability_constraint_wel, 7 | ) 8 | from imod.evaluate.head import ( 9 | calculate_gxg, 10 | calculate_gxg_points, 11 | convert_pointwaterhead_freshwaterhead, 12 | ) 13 | from imod.evaluate.streamfunction import ( 14 | quiver_line, 15 | quiver_linestring, 16 | streamfunction_line, 17 | streamfunction_linestring, 18 | ) 19 | -------------------------------------------------------------------------------- /imod/formats/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/imod-python/ecfb70a5ff832e83eb8927a3ef93d16d92b6ce89/imod/formats/__init__.py -------------------------------------------------------------------------------- /imod/formats/array_io/__init__.py: -------------------------------------------------------------------------------- 1 | from . import reading, writing 2 | -------------------------------------------------------------------------------- /imod/formats/gen/__init__.py: -------------------------------------------------------------------------------- 1 | from imod.formats.gen.gen import read, read_ascii, read_binary, write 2 | -------------------------------------------------------------------------------- /imod/formats/prj/__init__.py: -------------------------------------------------------------------------------- 1 | from imod.formats.prj.prj import open_projectfile_data, read_projectfile, read_timfile 2 | -------------------------------------------------------------------------------- /imod/logging/config.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | import imod 4 | 5 | from .loglevel import LogLevel 6 | from .logurulogger import LoguruLogger 7 | from .nulllogger import NullLogger 8 | from .pythonlogger import PythonLogger 9 | 10 | 11 | class LoggerType(Enum): 12 | """ 13 | The available logging frameworks. 14 | """ 15 | 16 | PYTHON = PythonLogger.__name__ 17 | """ 18 | The default python logging framework. 19 | """ 20 | LOGURU = LoguruLogger.__name__ 21 | """ 22 | The loguru logging framework. 23 | """ 24 | NULL = NullLogger.__name__ 25 | """ 26 | A dummy logger that doesn't log anything. 27 | """ 28 | 29 | 30 | def configure( 31 | logger_type: LoggerType, 32 | log_level: LogLevel = LogLevel.WARNING, 33 | add_default_stream_handler: bool = True, 34 | add_default_file_handler: bool = False, 35 | ) -> None: 36 | """ 37 | Setup the logging framework and assign it a log level. 38 | To add a default stream- and/or file-handler you can use the 39 | ``add_default_stream_handler`` or ``add_default_file_handler`` flags. If a 40 | default file-handler is added then the log output will be written to 41 | the `imod-python.log` file 42 | 43 | Parameters 44 | ---------- 45 | logger_type : LoggerType 46 | The logging framework to be used. 47 | log_level : LogLevel 48 | The log level to be set. 49 | add_default_stream_handler : bool 50 | A flag that specifies if a default stream-handler should be added. 51 | True by default. 52 | add_default_file_handler : bool 53 | A flag that specifies if a default filehandler should be added. 54 | The log will be written to `imod-python.log`. False by default. 55 | """ 56 | match logger_type: 57 | case LoggerType.PYTHON: 58 | imod.logging.logger.instance = PythonLogger( 59 | log_level, add_default_stream_handler, add_default_file_handler 60 | ) 61 | case LoggerType.LOGURU: 62 | imod.logging.logger.instance = LoguruLogger( 63 | log_level, add_default_stream_handler, add_default_file_handler 64 | ) 65 | case _: 66 | imod.logging.logger.instance = NullLogger() 67 | -------------------------------------------------------------------------------- /imod/logging/loglevel.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class LogLevel(Enum): 5 | """ 6 | The available log levels for the logger. 7 | """ 8 | 9 | DEBUG = 10 10 | """ 11 | A log level used for events considered to be useful during software 12 | debugging when more granular information is needed. 13 | """ 14 | INFO = 20 15 | """ 16 | An event happened, the event is purely informative and can be ignored 17 | during normal operations. 18 | """ 19 | WARNING = 30 20 | """ 21 | Unexpected behavior happened inside the application, but it is continuing 22 | its work and the key business features are operating as expected. 23 | """ 24 | ERROR = 40 25 | """ 26 | One or more functionalities are not working, preventing some functionalities 27 | from working correctly. 28 | """ 29 | CRITICAL = 50 30 | """ 31 | One or more key business functionalities are not working and the whole 32 | system doesn’t fulfill the business functionalities. 33 | """ 34 | -------------------------------------------------------------------------------- /imod/logging/nulllogger.py: -------------------------------------------------------------------------------- 1 | from imod.logging.ilogger import ILogger 2 | 3 | 4 | class NullLogger(ILogger): 5 | """ 6 | The :class:`NullLogger` is used as a dummy logger that doesn't log anything. 7 | """ 8 | 9 | def __init__(self) -> None: 10 | pass 11 | 12 | def debug(self, message: str, additional_depth: int = 0) -> None: 13 | pass 14 | 15 | def info(self, message: str, additional_depth: int = 0) -> None: 16 | pass 17 | 18 | def warning(self, message: str, additional_depth: int = 0) -> None: 19 | pass 20 | 21 | def error(self, message: str, additional_depth: int = 0) -> None: 22 | pass 23 | 24 | def critical(self, message: str, additional_depth: int = 0) -> None: 25 | pass 26 | -------------------------------------------------------------------------------- /imod/mf6/clipped_boundary_condition_creator.py: -------------------------------------------------------------------------------- 1 | import imod 2 | from imod.mf6 import ConstantHead 3 | from imod.select.grid import active_grid_boundary_xy 4 | from imod.typing import GridDataArray 5 | 6 | 7 | def create_clipped_boundary( 8 | idomain: GridDataArray, 9 | state_for_clipped_boundary: GridDataArray, 10 | original_constant_head_boundaries: list[ConstantHead], 11 | ) -> ConstantHead: 12 | """ 13 | Create a ConstantHead package on boundary cells that don't have any assigned to them. This is useful in 14 | combination with the clip_box method which can produce a domain with missing boundary conditions. 15 | 16 | Parameters 17 | ---------- 18 | idomain: 19 | The clipped domain 20 | state_for_clipped_boundary : 21 | The values to be assigned to the created ConstantHead package 22 | original_constant_head_boundaries : 23 | List of existing ConstantHead boundaries 24 | 25 | Returns 26 | ------- 27 | ConstantHead package providing values for boundary cells that are not 28 | covered by other ConstantHead packages 29 | 30 | """ 31 | active_grid_boundary = active_grid_boundary_xy(idomain > 0) 32 | unassigned_grid_boundaries = _find_unassigned_grid_boundaries( 33 | active_grid_boundary, original_constant_head_boundaries 34 | ) 35 | 36 | constant_head = state_for_clipped_boundary.where(unassigned_grid_boundaries) 37 | 38 | return imod.mf6.ConstantHead( 39 | constant_head, print_input=True, print_flows=True, save_flows=True 40 | ) 41 | 42 | 43 | def _find_unassigned_grid_boundaries( 44 | active_grid_boundary: GridDataArray, 45 | boundary_conditions: list[ConstantHead], 46 | ) -> GridDataArray: 47 | unassigned_grid_boundaries = active_grid_boundary 48 | for boundary_condition in boundary_conditions: 49 | unassigned_grid_boundaries = ( 50 | unassigned_grid_boundaries & boundary_condition["head"].isnull() 51 | ) 52 | 53 | return unassigned_grid_boundaries 54 | -------------------------------------------------------------------------------- /imod/mf6/gwfgwt.py: -------------------------------------------------------------------------------- 1 | from copy import deepcopy 2 | from typing import Optional, Self 3 | 4 | import cftime 5 | import numpy as np 6 | 7 | from imod.logging import init_log_decorator 8 | from imod.mf6.exchangebase import ExchangeBase 9 | from imod.mf6.package import Package 10 | from imod.typing import GridDataArray 11 | 12 | 13 | class GWFGWT(ExchangeBase): 14 | _pkg_id = "gwfgwt" 15 | 16 | _template = Package._initialize_template(_pkg_id) 17 | 18 | @init_log_decorator() 19 | def __init__(self, model_id1: str, model_id2: str): 20 | dict_dataset = { 21 | "model_name_1": model_id1, 22 | "model_name_2": model_id2, 23 | } 24 | 25 | super().__init__(dict_dataset) 26 | 27 | def clip_box( 28 | self, 29 | time_min: Optional[cftime.datetime | np.datetime64 | str] = None, 30 | time_max: Optional[cftime.datetime | np.datetime64 | str] = None, 31 | layer_min: Optional[int] = None, 32 | layer_max: Optional[int] = None, 33 | x_min: Optional[float] = None, 34 | x_max: Optional[float] = None, 35 | y_min: Optional[float] = None, 36 | y_max: Optional[float] = None, 37 | top: Optional[GridDataArray] = None, 38 | bottom: Optional[GridDataArray] = None, 39 | ) -> Self: 40 | """ 41 | The GWF-GWT exchange does not have any spatial coordinates that can be clipped. 42 | """ 43 | return deepcopy(self) 44 | -------------------------------------------------------------------------------- /imod/mf6/multimodel/partition_generator.py: -------------------------------------------------------------------------------- 1 | import textwrap 2 | from typing import Optional 3 | from warnings import warn 4 | 5 | from imod.mf6.simulation import Modflow6Simulation 6 | from imod.typing import GridDataArray 7 | 8 | 9 | def get_label_array( 10 | simulation: Modflow6Simulation, 11 | npartitions: int, 12 | weights: Optional[GridDataArray] = None, 13 | ): 14 | """ 15 | To preserve backwards compatibility for older training scripts. 16 | """ 17 | 18 | from imod.prepare.partition import create_partition_labels 19 | 20 | msg = textwrap.dedent( 21 | """get_label_array is deprecated, the function has been moved and 22 | renamed to imod.prepare.create_partition_labels.""" 23 | ) 24 | warn( 25 | msg, 26 | DeprecationWarning, 27 | ) 28 | return create_partition_labels( 29 | simulation=simulation, 30 | npartitions=npartitions, 31 | weights=weights, 32 | ) 33 | -------------------------------------------------------------------------------- /imod/mf6/out/common.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | import struct 3 | from typing import Any, BinaryIO, Dict, List, Union 4 | 5 | import numpy as np 6 | 7 | # Type annotations 8 | IntArray = np.ndarray 9 | FloatArray = np.ndarray 10 | FilePath = Union[str, pathlib.Path] 11 | 12 | 13 | def _grb_text(f: BinaryIO, lentxt: int = 50) -> str: 14 | return f.read(lentxt).decode("utf-8").strip().lower() 15 | 16 | 17 | def _to_nan(a: FloatArray, dry_nan: bool) -> FloatArray: 18 | # TODO: this could really use a docstring? 19 | a[a == 1e30] = np.nan 20 | if dry_nan: 21 | a[a == -1e30] = np.nan 22 | return a 23 | 24 | 25 | def get_first_header_advanced_package( 26 | headers: Dict[str, List[Any]], 27 | ) -> Any: 28 | for key, header_list in headers.items(): 29 | # multimodels have a gwf-gwf budget for flow-ja-face between domains 30 | if "flow-ja-face" not in key and "gwf_" in key: 31 | return header_list[0] 32 | return None 33 | 34 | 35 | def read_name_dvs(path: FilePath) -> str: 36 | """ 37 | Reads variable name from first header in dependent variable file. 38 | """ 39 | with open(path, "rb") as f: 40 | f.seek(24) 41 | name = struct.unpack("16s", f.read(16))[0] 42 | return name.decode().strip() 43 | 44 | 45 | def read_times_dvs(path: FilePath, ntime: int, indices: np.ndarray) -> FloatArray: 46 | """ 47 | Reads all total simulation times. 48 | """ 49 | times = np.empty(ntime, dtype=np.float64) 50 | 51 | # Compute how much to skip to the next timestamp 52 | start_of_header = 16 53 | rest_of_header = 28 54 | data_single_layer = indices.size * 8 55 | nskip = rest_of_header + data_single_layer + start_of_header 56 | 57 | with open(path, "rb") as f: 58 | f.seek(start_of_header) 59 | for i in range(ntime): 60 | times[i] = struct.unpack("d", f.read(8))[0] # total simulation time 61 | f.seek(nskip, 1) 62 | return times 63 | -------------------------------------------------------------------------------- /imod/mf6/regrid/__init__.py: -------------------------------------------------------------------------------- 1 | from imod.common.utilities.regrid_method_type import EmptyRegridMethod 2 | from imod.mf6.regrid.regrid_schemes import ( 3 | ConstantHeadRegridMethod, 4 | DiscretizationRegridMethod, 5 | DispersionRegridMethod, 6 | DrainageRegridMethod, 7 | EvapotranspirationRegridMethod, 8 | GeneralHeadBoundaryRegridMethod, 9 | InitialConditionsRegridMethod, 10 | MobileStorageTransferRegridMethod, 11 | NodePropertyFlowRegridMethod, 12 | RechargeRegridMethod, 13 | RiverRegridMethod, 14 | SpecificStorageRegridMethod, 15 | StorageCoefficientRegridMethod, 16 | ) 17 | -------------------------------------------------------------------------------- /imod/mf6/utilities/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/imod-python/ecfb70a5ff832e83eb8927a3ef93d16d92b6ce89/imod/mf6/utilities/__init__.py -------------------------------------------------------------------------------- /imod/mf6/utilities/chd_concat.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | import xarray as xr 4 | 5 | from imod.mf6.chd import ConstantHead 6 | 7 | 8 | def concat_layered_chd_packages( 9 | name: str, 10 | dict_packages: dict[str, ConstantHead], 11 | remove_merged_packages: bool = True, 12 | ) -> Optional[ConstantHead]: 13 | """ 14 | 15 | Parameters 16 | ---------- 17 | name: str 18 | The name of the package that was split over layers. 19 | If they are called "chd-1" and so on then set name to "chd" 20 | dict_packages: dict[str, ConstantHead] 21 | dictionary with package names as key and the packages as values 22 | remove_merged_packages: bool = True 23 | set to True to remove merged packages from dict_packages 24 | 25 | This function merges chd-packages whose name starts with "name" into a a 26 | single chd package. This is aimed at chd packages that are split over 27 | layers- so we would have chd-1, chd-2 and so on and these packages would 28 | define a chd package for layer 1, 2 and so on. This function merges them 29 | into a single chd package. If remove_merged_packages is True, then the 30 | packages that are concatenated are removed from the input dictionary, so 31 | that this on output only contains the packages that were not merged. 32 | """ 33 | 34 | candidate_keys = [k for k in dict_packages.keys() if name in k[0 : len(name)]] 35 | if len(candidate_keys) == 0: 36 | return None 37 | 38 | dataset_list = [] 39 | for key in candidate_keys: 40 | pack = dict_packages[key] 41 | dataset_list.append(pack.dataset) 42 | if remove_merged_packages: 43 | dict_packages.pop(key) 44 | 45 | concat_dataset = xr.concat( 46 | dataset_list, dim="layer", compat="equals", data_vars="different" 47 | ) 48 | return ConstantHead._from_dataset(concat_dataset) 49 | -------------------------------------------------------------------------------- /imod/mf6/utilities/dataset.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | import numpy as np 4 | import pandas as pd 5 | import xarray as xr 6 | 7 | from imod.typing import GridDataArray 8 | 9 | 10 | def remove_inactive(ds: xr.Dataset, active: xr.DataArray) -> xr.Dataset: 11 | """ 12 | Drop list-based input cells in inactive cells. 13 | 14 | Parameters 15 | ---------- 16 | ds: xr.Dataset 17 | Dataset with list-based input. Needs "cellid" variable. 18 | active: xr.DataArray 19 | Grid with active cells. 20 | """ 21 | 22 | def unstack_columns(array): 23 | # Unstack columns: 24 | # https://stackoverflow.com/questions/64097426/is-there-unstack-in-numpy 25 | # Make sure to use tuples, since these get the special treatment 26 | # which we require for the indexing: 27 | # https://numpy.org/doc/stable/user/basics.indexing.html#dealing-with-variable-numbers-of-indices-within-programs 28 | return tuple(np.moveaxis(array, -1, 0)) 29 | 30 | if "cellid" not in ds.data_vars: 31 | raise ValueError("Missing variable 'cellid' in dataset") 32 | if "ncellid" not in ds.dims: 33 | raise ValueError("Missing dimension 'ncellid' in dataset") 34 | 35 | cellid_zero_based = ds["cellid"].values - 1 36 | cellid_indexes = unstack_columns(cellid_zero_based) 37 | valid = active.values[cellid_indexes].astype(bool) 38 | 39 | return ds.loc[{"ncellid": valid}] 40 | 41 | 42 | def assign_datetime_coords( 43 | da: GridDataArray, 44 | simulation_start_time: np.datetime64, 45 | time_unit: Optional[str] = "d", 46 | ) -> GridDataArray: 47 | if "time" not in da.coords: 48 | raise ValueError( 49 | "cannot convert time column, because a time column could not be found" 50 | ) 51 | 52 | time = pd.Timestamp(simulation_start_time) + pd.to_timedelta( 53 | da["time"], unit=time_unit 54 | ) 55 | return da.assign_coords(time=time) 56 | -------------------------------------------------------------------------------- /imod/mf6/utilities/package.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import Optional 3 | 4 | import numpy as np 5 | import xarray as xr 6 | 7 | import imod 8 | from imod.common.interfaces.ipackage import IPackage 9 | from imod.util.expand_repetitions import expand_repetitions 10 | 11 | 12 | def get_repeat_stress(times) -> xr.DataArray: 13 | """ 14 | Set repeat stresses: re-use data of earlier periods. 15 | 16 | Parameters 17 | ---------- 18 | times: Dict of datetime-like to datetime-like. 19 | The data of the value datetime is used for the key datetime. 20 | """ 21 | keys = [ 22 | imod.util.time.to_datetime_internal(key, use_cftime=False) 23 | for key in times.keys() 24 | ] 25 | values = [ 26 | imod.util.time.to_datetime_internal(value, use_cftime=False) 27 | for value in times.values() 28 | ] 29 | return xr.DataArray( 30 | data=np.column_stack((keys, values)), 31 | dims=("repeat", "repeat_items"), 32 | ) 33 | 34 | 35 | def set_repeat_stress_if_available( 36 | repeat: Optional[list[datetime]], 37 | time_min: datetime, 38 | time_max: datetime, 39 | optional_package: Optional[IPackage], 40 | ) -> None: 41 | """Set repeat stress for optional package if repeat is not None.""" 42 | if repeat is not None: 43 | if optional_package is not None: 44 | times = expand_repetitions(repeat, time_min, time_max) 45 | optional_package.dataset["repeat_stress"] = get_repeat_stress(times) 46 | -------------------------------------------------------------------------------- /imod/mf6/validation_context.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class ValidationContext: 6 | validate: bool = True 7 | strict_well_validation: bool = True 8 | strict_hfb_validation: bool = True 9 | -------------------------------------------------------------------------------- /imod/msw/__init__.py: -------------------------------------------------------------------------------- 1 | from imod.msw.copy_files import FileCopier 2 | from imod.msw.coupler_mapping import CouplerMapping 3 | from imod.msw.grid_data import GridData 4 | from imod.msw.idf_mapping import IdfMapping 5 | from imod.msw.infiltration import Infiltration 6 | from imod.msw.initial_conditions import ( 7 | InitialConditionsEquilibrium, 8 | InitialConditionsPercolation, 9 | InitialConditionsRootzonePressureHead, 10 | InitialConditionsSavedState, 11 | ) 12 | from imod.msw.landuse import LanduseOptions 13 | from imod.msw.meteo_grid import MeteoGrid, MeteoGridCopy 14 | from imod.msw.meteo_mapping import EvapotranspirationMapping, PrecipitationMapping 15 | from imod.msw.model import MetaSwapModel 16 | from imod.msw.output_control import TimeOutputControl, VariableOutputControl 17 | from imod.msw.ponding import Ponding 18 | from imod.msw.scaling_factors import ScalingFactors 19 | from imod.msw.sprinkling import Sprinkling 20 | from imod.msw.vegetation import AnnualCropFactors 21 | -------------------------------------------------------------------------------- /imod/msw/copy_files.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from shutil import copy2 3 | from typing import cast 4 | 5 | import numpy as np 6 | import xarray as xr 7 | 8 | from imod.logging import logger 9 | from imod.logging.loglevel import LogLevel 10 | from imod.msw.pkgbase import MetaSwapPackage 11 | from imod.typing import Imod5DataDict 12 | 13 | _LOG_MESSAGE_TEMPLATE = """\ 14 | Will not copy files {filtered}, these will be generated by iMOD Python 15 | instead.""" 16 | 17 | 18 | class FileCopier(MetaSwapPackage): 19 | def __init__(self, paths: list[str]): 20 | super().__init__() 21 | paths_da = xr.DataArray( 22 | paths, coords={"file_nr": np.arange(len(paths))}, dims=("file_nr",) 23 | ) 24 | self.dataset["paths"] = paths_da 25 | 26 | @classmethod 27 | def from_imod5_data(cls, imod5_data: Imod5DataDict): 28 | paths = cast(list[list[str]], imod5_data["extra"]["paths"]) 29 | paths_unpacked = {Path(p[0]) for p in paths} 30 | files_to_filter = ( 31 | "mete_grid.inp", 32 | "para_sim.inp", 33 | "svat2precgrid.inp", 34 | "svat2etrefgrid.inp", 35 | ) 36 | paths_included = [ 37 | str(p) for p in paths_unpacked if p.name.lower() not in files_to_filter 38 | ] 39 | paths_excluded = {str(p) for p in paths_unpacked} - set(paths_included) 40 | if paths_excluded: 41 | log_message = _LOG_MESSAGE_TEMPLATE.format(filtered=paths_excluded) 42 | logger.log( 43 | loglevel=LogLevel.INFO, 44 | message=log_message, 45 | ) 46 | return cls(paths_included) 47 | 48 | def write(self, directory: str | Path, *_): 49 | directory = Path(directory) 50 | 51 | src_paths = [Path(p) for p in self.dataset["paths"].to_numpy()] 52 | dst_paths = [directory / p.name for p in src_paths] 53 | 54 | for src_path, dst_path in zip(src_paths, dst_paths): 55 | copy2(src_path, dst_path) 56 | -------------------------------------------------------------------------------- /imod/msw/timeutil.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | 4 | 5 | def to_metaswap_timeformat(times): 6 | """ 7 | Convert times to MetaSWAP's own time format, which consists of a year as 8 | integer and the number of days since the start of the year as float. 9 | 10 | Returns 11 | ------- 12 | tuple 13 | Consists of the year as integer and the number of days since the 14 | start of the year as float. 15 | 16 | """ 17 | 18 | # TODO: Also support cftime 19 | times = pd.DatetimeIndex(times) 20 | 21 | year = times.year 22 | 23 | # MetaSWAP requires a days since start year 24 | days_since_start_year = times.day_of_year.astype(np.float64) - 1.0 25 | # Behind the decimal is the time since start day 26 | time_since_start_day = times.hour / 24 + times.minute / 1440 + times.second / 86400 27 | 28 | time_since_start_year = days_since_start_year + time_since_start_day 29 | 30 | return year, time_since_start_year 31 | -------------------------------------------------------------------------------- /imod/msw/utilities/common.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from imod.typing import GridDataArray 4 | from imod.typing.grid import concat 5 | 6 | 7 | def concat_imod5(arg1: GridDataArray, arg2: GridDataArray) -> GridDataArray: 8 | return concat([arg1, arg2], dim="subunit").assign_coords(subunit=[0, 1]) 9 | 10 | 11 | def find_in_file_list(filename: str, paths: list[str]) -> str: 12 | for file in paths: 13 | if filename == Path(file[0]).name.lower(): 14 | return file[0] 15 | raise ValueError(f"could not find {filename} in list of paths: {paths}") 16 | -------------------------------------------------------------------------------- /imod/msw/utilities/mask.py: -------------------------------------------------------------------------------- 1 | import numbers 2 | from dataclasses import dataclass 3 | 4 | from imod.msw.pkgbase import MetaSwapPackage 5 | from imod.typing import GridDataArray, GridDataDict 6 | 7 | 8 | @dataclass 9 | class MetaSwapActive: 10 | all: GridDataArray 11 | per_subunit: GridDataArray 12 | 13 | 14 | @dataclass 15 | class MaskValues: 16 | """Stores sentinel values for nodata. Most cases use -9999.0, but exceptions 17 | can be added here.""" 18 | 19 | default = -9999.0 20 | integer = 0 21 | 22 | 23 | def mask_and_broadcast_cap_data( 24 | cap_data: GridDataDict, msw_active: MetaSwapActive 25 | ) -> GridDataDict: 26 | """ 27 | Mask and broadcast cap data, always mask with "all" of MetaSwapActive. 28 | """ 29 | return { 30 | key: _mask_spatial_var(grid, msw_active.all) for key, grid in cap_data.items() 31 | } 32 | 33 | 34 | def mask_and_broadcast_pkg_data( 35 | package: type[MetaSwapPackage], grid_data: GridDataDict, msw_active: MetaSwapActive 36 | ) -> GridDataDict: 37 | """ 38 | Mask and broadcast grid data, carefully mask per subunit if variable needs 39 | to contain subunits. 40 | """ 41 | 42 | return { 43 | key: ( 44 | _mask_spatial_var(grid, msw_active.per_subunit) 45 | if key in package._with_subunit 46 | else _mask_spatial_var(grid, msw_active.all) 47 | ) 48 | for key, grid in grid_data.items() 49 | } 50 | 51 | 52 | def _mask_spatial_var(da: GridDataArray, active: GridDataArray) -> GridDataArray: 53 | if issubclass(da.dtype.type, numbers.Integral): 54 | return da.where(active, other=MaskValues.integer) 55 | elif issubclass(da.dtype.type, numbers.Real): 56 | return da.where(active) 57 | else: 58 | raise TypeError( 59 | f"Expected dtype float or integer. Received instead: {da.dtype}" 60 | ) 61 | -------------------------------------------------------------------------------- /imod/msw/utilities/parse.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import TypeAlias 3 | 4 | ScalarType: TypeAlias = str | float | int 5 | 6 | 7 | def _try_parsing_string_to_number(s: str) -> ScalarType: 8 | """ 9 | Convert string to number: 10 | 11 | "1" -> 1 12 | "1.0" -> 1.0 13 | "a" -> "a" 14 | """ 15 | try: 16 | return int(s) 17 | except ValueError: 18 | pass 19 | try: 20 | return float(s) 21 | except ValueError: 22 | pass 23 | return s 24 | 25 | 26 | def correct_unsa_svat_path(unsa_path: ScalarType) -> str: 27 | """ 28 | Correct the path to the UNSA SVAT executable in the parameter file. Drop any 29 | quotes and trailing backslashes, and replace any dollar signs. These could 30 | have been added by ``MetaSwapModel._render_unsaturated_database_path``. 31 | """ 32 | if not isinstance(unsa_path, str): 33 | raise TypeError(f"Unexcepted type for unsa_path, expected str, got {unsa_path}") 34 | 35 | unsa_path = unsa_path.replace('"', "") 36 | 37 | if unsa_path.endswith("\\"): 38 | unsa_path = unsa_path[:-1] 39 | 40 | if unsa_path.startswith("$"): 41 | unsa_path = unsa_path.replace("$", "./") 42 | 43 | return unsa_path 44 | 45 | 46 | def read_para_sim(file: Path | str) -> dict[str, ScalarType]: 47 | with open(file, "r") as f: 48 | lines = f.readlines() 49 | out = {} 50 | for line in lines: 51 | # Strip comments starting with "!" and split keys from values at the 52 | # equals sign. 53 | key_values = line[0 : line.find("!")].split("=") 54 | if len(key_values) > 1: 55 | key = key_values[0].strip() 56 | value = _try_parsing_string_to_number(key_values[1].strip()) 57 | out[key] = value 58 | 59 | out["unsa_svat_path"] = correct_unsa_svat_path(out["unsa_svat_path"]) 60 | 61 | return out 62 | -------------------------------------------------------------------------------- /imod/prepare/laplace.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | import numpy as np 4 | from scipy import sparse 5 | from xugrid.ugrid.interpolate import ILU0Preconditioner 6 | 7 | 8 | def _build_connectivity(shape): 9 | # Get the Cartesian neighbors for a finite difference approximation. 10 | size = np.prod(shape) 11 | index = np.arange(size).reshape(shape) 12 | 13 | # Build nD connectivity 14 | ii = [] 15 | jj = [] 16 | for d in range(len(shape)): 17 | slices = [slice(None)] * len(shape) 18 | 19 | slices[d] = slice(None, -1) 20 | left = index[tuple(slices)].ravel() 21 | slices[d] = slice(1, None) 22 | right = index[tuple(slices)].ravel() 23 | ii.extend([left, right]) 24 | jj.extend([right, left]) 25 | 26 | i = np.concatenate(ii) 27 | j = np.concatenate(jj) 28 | return sparse.coo_matrix((np.ones(len(i)), (i, j)), shape=(size, size)).tocsr() 29 | 30 | 31 | def _interpolate( 32 | arr: np.ndarray, 33 | connectivity: sparse.csr_matrix, 34 | direct: bool, 35 | delta: float, 36 | relax: float, 37 | rtol: float, 38 | atol: float, 39 | maxiter: int, 40 | ): 41 | ar1d = arr.ravel() 42 | unknown = np.isnan(ar1d) 43 | known = ~unknown 44 | 45 | # Set up system of equations. 46 | matrix = connectivity.copy() 47 | matrix.setdiag(-matrix.sum(axis=1).A[:, 0]) 48 | rhs = -matrix[:, known].dot(ar1d[known]) 49 | 50 | # Linear solve for the unknowns. 51 | A = matrix[unknown][:, unknown] 52 | b = rhs[unknown] 53 | if direct: 54 | x = sparse.linalg.spsolve(A, b) 55 | else: # Preconditioned conjugate-gradient linear solve. 56 | # Create preconditioner M 57 | M = ILU0Preconditioner.from_csr_matrix(A, delta=delta, relax=relax) 58 | # Call conjugate gradient solver 59 | x, info = sparse.linalg.cg(A, b, rtol=rtol, atol=atol, maxiter=maxiter, M=M) 60 | if info < 0: 61 | raise ValueError("scipy.sparse.linalg.cg: illegal input or breakdown") 62 | elif info > 0: 63 | warnings.warn(f"Failed to converge after {maxiter} iterations") 64 | 65 | out = ar1d.copy() 66 | out[unknown] = x 67 | return out.reshape(arr.shape) 68 | -------------------------------------------------------------------------------- /imod/prepare/surface_water.py: -------------------------------------------------------------------------------- 1 | # Import for backwards compatibility 2 | import warnings 3 | 4 | from imod.prepare.topsystem import c_leakage as _c_leakage 5 | from imod.prepare.topsystem import c_radial as _c_radial 6 | 7 | WARNING_MESSAGE = ( 8 | "function has been moved from imod.prepare.surface_water to" 9 | "imod.prepare.topsystem, please update your scripts." 10 | "imod.prepare.surface_water is going to be removed in version 1.0" 11 | ) 12 | 13 | 14 | def c_radial(L, kh, kv, B, D): 15 | warnings.warn(WARNING_MESSAGE, DeprecationWarning) 16 | return _c_radial(L, kh, kv, B, D) 17 | 18 | 19 | def c_leakage(kh, kv, D, c0, c1, B, length, dx, dy): 20 | warnings.warn(WARNING_MESSAGE, DeprecationWarning) 21 | return _c_leakage(kh, kv, D, c0, c1, B, length, dx, dy) 22 | -------------------------------------------------------------------------------- /imod/prepare/topsystem/__init__.py: -------------------------------------------------------------------------------- 1 | from imod.prepare.topsystem.allocation import ( 2 | ALLOCATION_OPTION, 3 | allocate_drn_cells, 4 | allocate_ghb_cells, 5 | allocate_rch_cells, 6 | allocate_riv_cells, 7 | ) 8 | from imod.prepare.topsystem.conductance import ( 9 | DISTRIBUTING_OPTION, 10 | distribute_drn_conductance, 11 | distribute_ghb_conductance, 12 | distribute_riv_conductance, 13 | split_conductance_with_infiltration_factor, 14 | ) 15 | from imod.prepare.topsystem.default_allocation_methods import ( 16 | SimulationAllocationOptions, 17 | SimulationDistributingOptions, 18 | ) 19 | from imod.prepare.topsystem.resistance import c_leakage, c_radial 20 | -------------------------------------------------------------------------------- /imod/select/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Get points, cross sections and layers. 3 | """ 4 | 5 | from imod.select.cross_sections import cross_section_line, cross_section_linestring 6 | from imod.select.grid import active_grid_boundary_xy, grid_boundary_xy 7 | from imod.select.layers import upper_active_layer 8 | from imod.select.points import ( 9 | points_in_bounds, 10 | points_indices, 11 | points_set_values, 12 | points_values, 13 | ) 14 | -------------------------------------------------------------------------------- /imod/select/layers.py: -------------------------------------------------------------------------------- 1 | def upper_active_layer(da, is_ibound=True, include_constant_head=False): 2 | """ 3 | Function to get the upper active layer from ibound xarray.DataArray 4 | 5 | Parameters 6 | ---------- 7 | da : xarray.DataArray 8 | A 3D DataArray 9 | is_ibound: bool, optional 10 | If True, ``da`` is interpreted as ibound, with values 0: inactive, 1: active, -1 constant head. 11 | If False, ``upper_active_layer`` is interpreted as first layer that has data. 12 | Default is True. 13 | include_constant_head : bool, optional 14 | If True and ``is_ibound``, also include constant head cells. 15 | Default is False. 16 | 17 | Returns 18 | ------- 19 | 2d xr.DataArray of layernumber of upper active model layer 20 | """ 21 | da = da.load() 22 | if is_ibound: 23 | # check if indeed ibound: convertible to int 24 | if not da.astype(int).equals(da): 25 | raise ValueError( 26 | "Passed DataArray is no ibound, while is_bound was set to True" 27 | ) 28 | # include constant head cells (?) 29 | if include_constant_head: 30 | is_active = da.fillna(0) != 0 # must be filled for argmax 31 | else: 32 | is_active = da.fillna(0) > 0 33 | else: 34 | is_active = ~da.isnull() 35 | 36 | # get layer of upper active cell 37 | da = is_active.layer.isel(layer=is_active.argmax(dim="layer")) 38 | da = da.drop_vars("layer") 39 | 40 | # skip where no active cells 41 | return da.where(is_active.sum(dim="layer") > 0) 42 | -------------------------------------------------------------------------------- /imod/templates/mf6/api.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | {% if print_input is defined %} print_input 3 | {% endif %} 4 | {%- if print_flows is defined %} print_flows 5 | {% endif %} 6 | {%- if save_flows is defined %} save_flows 7 | {% endif -%} 8 | end options 9 | 10 | begin dimensions 11 | maxbound {{maxbound}} 12 | end dimensions -------------------------------------------------------------------------------- /imod/templates/mf6/exg-gwfgwf.j2: -------------------------------------------------------------------------------- 1 | # this file contains the exchanges from model {{model_name_1}} to model {{model_name_2}} 2 | 3 | begin options 4 | {% if auxiliary is defined %} auxiliary {{auxiliary|join(" ")}} 5 | {% endif -%} 6 | {%- if print_input is defined -%} print_input 7 | {% endif -%} 8 | {%- if print_flows is defined -%} print_flows 9 | {% endif -%} 10 | {%- if save_flows is defined -%} save_flows 11 | {% endif -%} 12 | {%- if cell_averaging is defined -%} cell_averaging {{cell_averaging}} 13 | {% endif -%} 14 | {%- if variablecv is defined -%} variablecv {% if dewatered is defined %} dewatered {% endif%} 15 | {% endif -%} 16 | {%- if newton is defined -%} newton 17 | {% endif -%} 18 | {%- if gnc is defined -%} gnc6 filein {{gnc}} 19 | {% endif -%} 20 | {%- if xt3d is defined -%} xt3d 21 | {% endif -%} 22 | {%- if mvr is defined -%} mvr6 filein {{mvr}} 23 | {% endif -%} 24 | {%- if obs is defined -%} obs6 filein {{obs}} 25 | {% endif -%} 26 | end options 27 | {% set nexg = layer | length %} 28 | begin dimensions 29 | nexg {{nexg}} 30 | end dimensions 31 | 32 | begin exchangedata 33 | # first 3 (structured) or 2 (unstructured) columns are the exchange boundary cell indices in the numbering local to {{model_name_1}} 34 | # second 3 (structured) or 2 (unstructured) columns are the exchange boundary cell indices in the numbering local to {{model_name_2}} 35 | # followed by columns ihc, cl1, cl2, hwva and auxiliary variables, if any 36 | {{datablock}} 37 | end exchangedata 38 | -------------------------------------------------------------------------------- /imod/templates/mf6/exg-gwfgwt.j2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/imod-python/ecfb70a5ff832e83eb8927a3ef93d16d92b6ce89/imod/templates/mf6/exg-gwfgwt.j2 -------------------------------------------------------------------------------- /imod/templates/mf6/exg-gwtgwt.j2: -------------------------------------------------------------------------------- 1 | # this file contains the exchanges from model {{model_name_1}} to model {{model_name_2}} 2 | 3 | begin options 4 | gwfmodelname1 {{flow_model_name_1}} 5 | gwfmodelname2 {{flow_model_name_2}} 6 | {% if auxiliary is defined %} auxiliary {{auxiliary|join(" ")}} 7 | {% endif -%} 8 | {%- if print_input is defined -%} print_input 9 | {% endif -%} 10 | {%- if print_flows is defined -%} print_flows 11 | {% endif -%} 12 | {%- if save_flows is defined -%} save_flows 13 | {% endif -%} 14 | {%- if adv_scheme is defined -%} adv_scheme {{adv_scheme}} 15 | {% endif -%} 16 | {%- if dsp_xt3d_off is defined -%} dsp_xt3d_off 17 | {% endif -%} 18 | {%- if dsp_xt3d_rhs is defined -%} dsp_xt3d_rhs 19 | {% endif -%} 20 | {%- if mvt is defined -%} mvt6 filein {{mvt}} 21 | {% endif -%} 22 | {%- if obs is defined -%} obs6 filein {{obs}} 23 | {% endif -%} 24 | end options 25 | {% set nexg = layer | length %} 26 | begin dimensions 27 | nexg {{nexg}} 28 | end dimensions 29 | 30 | begin exchangedata 31 | # first 3 (structured) or 2 (unstructured) columns are the exchange boundary cell indices in the numbering local to {{model_name_1}} 32 | # second 3 (structured) or 2 (unstructured) columns are the exchange boundary cell indices in the numbering local to {{model_name_2}} 33 | # followed by columns ihc, cl1, cl2, hwva and auxiliary variables, if any 34 | {{datablock}} 35 | end exchangedata 36 | -------------------------------------------------------------------------------- /imod/templates/mf6/gwf-buy.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | {%- if hhformulation_rhs is defined %} 3 | hhformulation_rhs{% endif %} 4 | {%- if reference_density is defined %} 5 | denseref {{reference_density}}{% endif %} 6 | {%- if densityfile is defined %} 7 | density fileout {{densityfile}}{% endif %} 8 | end options 9 | 10 | begin dimensions 11 | nrhospecies {{nrhospecies}} 12 | end dimensions 13 | 14 | begin packagedata 15 | {%- for irhospec, drhodc, crhoref, modelname, auxspeciesname in packagedata %} 16 | {{irhospec}} {{drhodc}} {{crhoref}} {{modelname}} {{auxspeciesname}}{% endfor %} 17 | end packagedata -------------------------------------------------------------------------------- /imod/templates/mf6/gwf-chd.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | {% if auxiliary is defined %} auxiliary{% for aux in auxiliary %} {{aux}}{% endfor %} 3 | {% endif %} 4 | {%- if auxmultname is defined %} auxmultname {{auxmultname}} 5 | {% endif %} 6 | {%- if boundnames is defined %} boundnames 7 | {% endif %} 8 | {%- if print_input is defined %} print_input 9 | {% endif %} 10 | {%- if print_flows is defined %} print_flows 11 | {% endif %} 12 | {%- if save_flows is defined %} save_flows 13 | {% endif %} 14 | {%- if ts_filerecord is defined %} ts6 filein {{ts6_filename}} 15 | {% endif %} 16 | {%- if obs_filerecord is defined %} obs6 filein {{obs6_filename}} 17 | {% endif -%} 18 | end options 19 | 20 | begin dimensions 21 | maxbound {{maxbound}} 22 | end dimensions 23 | 24 | {% for i, path in periods.items() %}begin period {{i}} 25 | open/close {{path}}{% if binary %} (binary){% endif %} 26 | end period 27 | {% endfor %} -------------------------------------------------------------------------------- /imod/templates/mf6/gwf-cnc.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | {% if auxiliary is defined %} auxiliary {{auxiliary}} 3 | {% endif %} 4 | {%- if auxmultname is defined %} auxmultname {{auxmultname}} 5 | {% endif %} 6 | {%- if boundnames is defined %} boundnames 7 | {% endif %} 8 | {%- if print_input is defined %} print_input 9 | {% endif %} 10 | {%- if print_flows is defined %} print_flows 11 | {% endif %} 12 | {%- if save_flows is defined %} save_flows 13 | {% endif %} 14 | {%- if ts_filerecord is defined %} ts6 filein {{ts6_filename}} 15 | {% endif %} 16 | {%- if obs_filerecord is defined %} obs6 filein {{obs6_filename}} 17 | {% endif %} 18 | {%- if mover is defined %} mover 19 | {% endif -%} 20 | end options 21 | 22 | begin dimensions 23 | maxbound {{maxbound}} 24 | end dimensions 25 | 26 | {% for i, path in periods.items() %}begin period {{i}} 27 | open/close {{path}}{% if binary %} (binary){% endif %} 28 | end period{% endfor %} -------------------------------------------------------------------------------- /imod/templates/mf6/gwf-dis.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | {% if length_units is defined %} length_units {{length_units}} 3 | {% endif %} 4 | {%- if nogrb is defined %} nogrb 5 | {% endif %} 6 | {%- if xorigin is defined %} xorigin {{xorigin}} 7 | {% endif %} 8 | {%- if yorigin is defined %} yorigin {{yorigin}} 9 | {% endif %} 10 | {%- if angrot is defined %} angrot {{angrot}} 11 | {% endif -%} 12 | end options 13 | 14 | begin dimensions 15 | nlay {{nlay}} 16 | nrow {{nrow}} 17 | ncol {{ncol}} 18 | end dimensions 19 | 20 | begin griddata 21 | delr 22 | {{delr}} 23 | delc 24 | {{delc}} 25 | top 26 | {% for x in top %} {{x}} 27 | {% endfor %} botm{% if botm_layered %} layered{% endif %} 28 | {% for x in botm %} {{x}} 29 | {% endfor %}{% if idomain is defined %} idomain{% if idomain_layered %} layered{% endif %} 30 | {% for x in idomain %} {{x}} 31 | {% endfor -%} 32 | {% endif -%} 33 | end griddata 34 | -------------------------------------------------------------------------------- /imod/templates/mf6/gwf-disu.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | {%- if length_units is defined -%} length_units {{length_units}}{%- endif -%} 3 | {%- if nogrb is defined -%} nogrb{%- endif -%} 4 | {%- if xorigin is defined -%} xorigin {{xorigin}}{%- endif -%} 5 | {%- if yorigin is defined -%} yorigin {{yorigin}}{%- endif -%} 6 | {%- if angrot is defined -%} angrot {{angrot}}{%- endif -%} 7 | end options 8 | 9 | begin dimensions 10 | nodes {{nodes}} 11 | nja {{nja}} 12 | {%- if nvert is defined -%} nvert {{nvert}}{%- endif -%} 13 | end dimensions 14 | 15 | begin griddata 16 | top 17 | {top} 18 | bot 19 | {bot} 20 | area 21 | {area} 22 | end griddata 23 | 24 | begin connectiondata 25 | iac 26 | {iac} 27 | ja 28 | {ja} 29 | ihc 30 | {ihc} 31 | cl12 32 | {cl12} 33 | hwva 34 | {hwva} 35 | {%- if angldegx is defined -%} angldegx 36 | {angldegx}{%- endif -%} 37 | end connectiondata 38 | 39 | begin vertices 40 | {{iv}} {{xv}} {{yv}} 41 | {{iv}} {{xv}} {{yv}} 42 | ... 43 | end vertices 44 | 45 | begin cell2d 46 | {{icell2d}} {{xc}} {{yc}} {{ncvert}} {{icvert(ncvert)}} 47 | {{icell2d}} {{xc}} {{yc}} {{ncvert}} {{icvert(ncvert)}} 48 | ... 49 | end cell2d 50 | -------------------------------------------------------------------------------- /imod/templates/mf6/gwf-disv.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | {% if length_units is defined %} length_units {{length_units}} 3 | {% endif %} 4 | {%- if nogrb is defined %} nogrb 5 | {% endif %} 6 | {%- if xorigin is defined %} xorigin {{xorigin}} 7 | {% endif %} 8 | {%- if yorigin is defined %} yorigin {{yorigin}} 9 | {% endif %} 10 | {%- if angrot is defined %} angrot {{angrot}} 11 | {% endif -%} 12 | end options 13 | 14 | begin dimensions 15 | nlay {{nlay}} 16 | ncpl {{ncpl}} 17 | nvert {{nvert}} 18 | end dimensions 19 | 20 | begin griddata 21 | top 22 | {% for x in top %} {{x}} 23 | {% endfor %} botm{% if botm_layered %} layered{% endif %} 24 | {% for x in botm %} {{x}} 25 | {% endfor %}{% if idomain is defined %} idomain{% if idomain_layered %} layered{% endif %} 26 | {% for x in idomain %} {{x}} 27 | {% endfor -%} 28 | {% endif -%} 29 | end griddata -------------------------------------------------------------------------------- /imod/templates/mf6/gwf-drn.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | {% if auxiliary is defined %} auxiliary{% for aux in auxiliary %} {{aux}}{% endfor %} 3 | {% endif %} 4 | {%- if auxmultname is defined %} auxmultname {{auxmultname}} 5 | {% endif %} 6 | {%- if boundnames is defined %} boundnames 7 | {% endif %} 8 | {%- if print_input is defined %} print_input 9 | {% endif %} 10 | {%- if print_flows is defined %} print_flows 11 | {% endif %} 12 | {%- if save_flows is defined %} save_flows 13 | {% endif %} 14 | {%- if ts_filerecord is defined %} ts6 filein {{ts6_filename}} 15 | {% endif %} 16 | {%- if obs_filerecord is defined %} obs6 filein {{obs6_filename}} 17 | {% endif %} 18 | {%- if mover is defined %} mover 19 | {% endif -%} 20 | end options 21 | 22 | begin dimensions 23 | maxbound {{maxbound}} 24 | end dimensions 25 | 26 | {% for i, path in periods.items() %}begin period {{i}} 27 | open/close {{path}}{% if binary %} (binary){% endif %} 28 | end period 29 | {% endfor %} -------------------------------------------------------------------------------- /imod/templates/mf6/gwf-evt.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | {% if fixed_cell is defined %} fixed_cell 3 | {% endif %} 4 | {%- if auxiliary is defined %} auxiliary{% for aux in auxiliary %} {{aux}}{% endfor %}{% endif %} 5 | {%- if auxmultname is defined %} auxmultname {{auxmultname}}{% endif %} 6 | {%- if boundnames is defined %} boundnames{% endif %} 7 | {%- if print_input is defined %} print_input{% endif %} 8 | {%- if print_flows is defined %} print_flows{% endif %} 9 | {%- if save_flows is defined %} save_flows{% endif %} 10 | {%- if ts_filerecord is defined %} ts6 filein {{ts6_filename}}{% endif %} 11 | {%- if obs_filerecord is defined %} obs6 filein {{obs6_filename}}{% endif %} 12 | {%- if surf_rate_specified is defined %} surf_rate_specified{% endif %} 13 | end options 14 | 15 | begin dimensions 16 | maxbound {{maxbound}} 17 | {% if nseg is defined %} nseg {{nseg}}{% endif %} 18 | end dimensions 19 | 20 | {% for i, path in periods.items() %} 21 | begin period {{i}} 22 | open/close {{path}}{% if binary %} (binary){% endif %} 23 | end period{% endfor %} -------------------------------------------------------------------------------- /imod/templates/mf6/gwf-evta.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | readasarrays 3 | {%- if fixed_cell is defined -%} fixed_cell{%- endif -%} 4 | {%- if auxiliary is defined -%} auxiliary {{auxiliary(naux)}}{%- endif -%} 5 | {%- if auxmultname is defined -%} auxmultname {{auxmultname}}{%- endif -%} 6 | {%- if print_input is defined -%} print_input{%- endif -%} 7 | {%- if print_flows is defined -%} print_flows{%- endif -%} 8 | {%- if save_flows is defined -%} save_flows{%- endif -%} 9 | {%- if tas_filerecord is defined -%} tas6 filein {{tas6_filename}}{%- endif -%} 10 | {%- if obs_filerecord is defined -%} obs6 filein {{obs6_filename}}{%- endif -%} 11 | end options 12 | 13 | {% for i, path in periods.items() %}begin period {{i}} 14 | open/close {{path}}{% if binary %} (binary){% endif %} 15 | end period{% endfor %} -------------------------------------------------------------------------------- /imod/templates/mf6/gwf-ghb.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | {% if auxiliary is defined %} auxiliary{% for aux in auxiliary %} {{aux}}{% endfor %} 3 | {% endif %} 4 | {%- if auxmultname is defined %} auxmultname {{auxmultname}} 5 | {% endif %} 6 | {%- if boundnames is defined %} boundnames 7 | {% endif %} 8 | {%- if print_input is defined %} print_input 9 | {% endif %} 10 | {%- if print_flows is defined %} print_flows 11 | {% endif %} 12 | {%- if save_flows is defined %} save_flows 13 | {% endif %} 14 | {%- if ts_filerecord is defined %} ts6 filein {{ts6_filename}} 15 | {% endif %} 16 | {%- if obs_filerecord is defined %} obs6 filein {{obs6_filename}} 17 | {% endif %} 18 | {%- if mover is defined %} mover 19 | {% endif -%} 20 | end options 21 | 22 | begin dimensions 23 | maxbound {{maxbound}} 24 | end dimensions 25 | 26 | {% for i, path in periods.items() %}begin period {{i}} 27 | open/close {{path}}{% if binary %} (binary){% endif %} 28 | end period 29 | {% endfor %} -------------------------------------------------------------------------------- /imod/templates/mf6/gwf-gnc.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | {%- if print_input is defined -%} print_input{%- endif -%} 3 | {%- if print_flows is defined -%} print_flows{%- endif -%} 4 | {%- if explicit is defined -%} explicit{%- endif -%} 5 | end options 6 | 7 | begin dimensions 8 | numgnc {{numgnc}} 9 | numalphaj {{numalphaj}} 10 | end dimensions 11 | 12 | begin gncdata 13 | {{cellidn}} {{cellidm}} {{cellidsj(numalphaj)}} {{alphasj(numalphaj)}} 14 | {{cellidn}} {{cellidm}} {{cellidsj(numalphaj)}} {{alphasj(numalphaj)}} 15 | ... 16 | end gncdata 17 | -------------------------------------------------------------------------------- /imod/templates/mf6/gwf-hfb.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | {% if print_input is defined %} print_input{% endif %} 3 | end options 4 | 5 | begin dimensions 6 | maxhfb {{maxbound}} 7 | end dimensions 8 | 9 | {% for i, path in periods.items() %}begin period {{i}} 10 | open/close {{path}}{% if binary %} (binary){% endif %} 11 | end period{% endfor %} -------------------------------------------------------------------------------- /imod/templates/mf6/gwf-ic.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | end options 3 | 4 | begin griddata 5 | strt{% if strt_layered %} layered{% endif %} 6 | {% for x in strt %} {{x}} 7 | {% endfor -%} 8 | end griddata 9 | -------------------------------------------------------------------------------- /imod/templates/mf6/gwf-lak.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | {% if auxiliary is defined %} auxiliary{% for aux in auxiliary %} {{aux}}{% endfor %} 3 | {% endif %} 4 | {%- if boundnames is defined -%} boundnames 5 | {% endif -%} 6 | {%- if print_input is defined -%} print_input 7 | {% endif -%} 8 | {%- if print_stage is defined -%} print_stage 9 | {% endif -%} 10 | {%- if print_flows is defined -%} print_flows 11 | {% endif -%} 12 | {%- if save_flows is defined -%} save_flows 13 | {% endif -%} 14 | {%- if stage_filerecord is defined -%}stage fileout {{stagefile}} 15 | {% endif -%} 16 | {%- if budget_filerecord is defined -%} budget fileout {{budgetfile}} 17 | {% endif -%} 18 | {%- if ts_filerecord is defined -%} ts6 filein {{ts6_filename}} 19 | {% endif -%} 20 | {%- if obs_filerecord is defined -%} obs6 filein {{obs6_filename}} 21 | {% endif -%} 22 | {%- if mover is defined -%} mover 23 | {% endif -%} 24 | {%- if surfdep is defined -%} surfdep {{surfdep}} 25 | {% endif -%} 26 | {%- if time_conversion is defined -%} time_conversion {{time_conversion}} 27 | {% endif -%} 28 | {%- if length_conversion is defined -%} length_conversion {{length_conversion}} 29 | {% endif -%} 30 | end options 31 | 32 | begin dimensions 33 | nlakes {{nlakes}} 34 | noutlets {{noutlets}} 35 | ntables {{ntables}} 36 | end dimensions 37 | 38 | begin packagedata{% for number, stage, nconn, name in packagedata %} 39 | {{number}} {{stage}} {{nconn}} {{name}}{% endfor %} 40 | end packagedata 41 | -------------------------------------------------------------------------------- /imod/templates/mf6/gwf-laketable.j2: -------------------------------------------------------------------------------- 1 | BEGIN DIMENSIONS 2 | NROW {{nrow}} 3 | NCOL {{ncol}} 4 | END DIMENSIONS 5 | begin table 6 | {{table}}end table -------------------------------------------------------------------------------- /imod/templates/mf6/gwf-maw.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | {%- if auxiliary is defined -%} auxiliary {{auxiliary(naux)}}{%- endif -%} 3 | {%- if boundnames is defined -%} boundnames{%- endif -%} 4 | {%- if print_input is defined -%} print_input{%- endif -%} 5 | {%- if print_head is defined -%} print_head{%- endif -%} 6 | {%- if print_flows is defined -%} print_flows{%- endif -%} 7 | {%- if save_flows is defined -%} save_flows{%- endif -%} 8 | {%- if stage_filerecord is defined -%} head fileout {{headfile}}{%- endif -%} 9 | {%- if budget_filerecord is defined -%} budget fileout {{budgetfile}}{%- endif -%} 10 | {%- if no_well_storage is defined -%} no_well_storage{%- endif -%} 11 | {%- if flowing_wells is defined -%} flowing_wells{%- endif -%} 12 | {%- if shutdown_theta is defined -%} shutdown_theta {{shutdown_theta}}{%- endif -%} 13 | {%- if shutdown_kappa is defined -%} shutdown_kappa {{shutdown_kappa}}{%- endif -%} 14 | {%- if ts_filerecord is defined -%} ts6 filein {{ts6_filename}}{%- endif -%} 15 | {%- if obs_filerecord is defined -%} obs6 filein {{obs6_filename}}{%- endif -%} 16 | {%- if mover is defined -%} mover{%- endif -%} 17 | end options 18 | 19 | begin dimensions 20 | nmawwells {{nmawwells}} 21 | end dimensions 22 | 23 | begin packagedata 24 | {{wellno}} {{radius}} {{bottom}} {{strt}} {{condeqn}} {{ngwfnodes}} {%- if aux is defined -%}{{aux(naux)}}{%- endif -%} {%- if boundname is defined -%}{{boundname}}{%- endif -%} 25 | {{wellno}} {{radius}} {{bottom}} {{strt}} {{condeqn}} {{ngwfnodes}} {%- if aux is defined -%}{{aux(naux)}}{%- endif -%} {%- if boundname is defined -%}{{boundname}}{%- endif -%} 26 | ... 27 | end packagedata 28 | 29 | begin connectiondata 30 | {{wellno}} {{icon}} {{cellid(ncelldim)}} {{scrn_top}} {{scrn_bot}} {{hk_skin}} {{radius_skin}} 31 | {{wellno}} {{icon}} {{cellid(ncelldim)}} {{scrn_top}} {{scrn_bot}} {{hk_skin}} {{radius_skin}} 32 | ... 33 | end connectiondata 34 | 35 | {% for i, path in periods.items() %}begin period {{i}} 36 | open/close {{path}} (binary) 37 | end period{% endfor %} -------------------------------------------------------------------------------- /imod/templates/mf6/gwf-mvr.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | {%- if print_input is defined -%} print_input{%- endif -%} 3 | {%- if print_flows is defined -%} print_flows{%- endif -%} 4 | {%- if modelnames is defined -%} modelnames{%- endif -%} 5 | {%- if budget_filerecord is defined -%} budget fileout {{budgetfile}}{%- endif -%} 6 | end options 7 | 8 | begin dimensions 9 | maxmvr {{maxmvr}} 10 | maxpackages {{maxpackages}} 11 | end dimensions 12 | 13 | begin packages 14 | {%- if mname is defined -%}{{mname}}{%- endif -%} {{pname}} 15 | {%- if mname is defined -%}{{mname}}{%- endif -%} {{pname}} 16 | ... 17 | end packages 18 | 19 | {% for i, path in periods.items() %}begin period {{i}} 20 | open/close {{path}} (binary) 21 | end period{% endfor %} -------------------------------------------------------------------------------- /imod/templates/mf6/gwf-nam.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | {% if listing_file is defined %} list {{listing_file}} 3 | {% endif %} 4 | {%- if print_input is defined %} print_input 5 | {% endif %} 6 | {%- if print_flows is defined %} print_flows 7 | {% endif %} 8 | {%- if save_flows is defined %} save_flows 9 | {% endif %} 10 | {%- if newton is defined %} newton {% if under_relaxation is defined %}under_relaxation{% endif %} 11 | {% endif -%} 12 | end options 13 | 14 | begin packages 15 | {% for (ftype, fname, pkgname) in packages%} {{ftype}} {{fname}} {{pkgname}} 16 | {% endfor -%} 17 | end packages 18 | -------------------------------------------------------------------------------- /imod/templates/mf6/gwf-npf.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | {% if save_flows is defined %} save_flows 3 | {% endif %} 4 | {%- if alternative_cell_averaging is defined %} alternative_cell_averaging {{alternative_cell_averaging}} 5 | {% endif %} 6 | {%- if thickstrt is defined %} thickstrt 7 | {% endif %} 8 | {%- if variablecv is defined %} variablecv{% if dewatered is defined %} dewatered{% endif %} 9 | {% endif %} 10 | {%- if perched is defined %} perched 11 | {% endif %} 12 | {%- if rewet_record is defined %} rewet wetfct {{wetfct}} iwetit {{iwetit}} ihdwet {{ihdwet}} 13 | {% endif %} 14 | {%- if xt3doptions is defined %} xt3d{% if rhs is defined %} rhs{% endif %} 15 | {% endif %} 16 | {%- if save_specific_discharge is defined %} save_specific_discharge 17 | {% endif -%} 18 | {%- if save_saturation is defined %} save_saturation 19 | {% endif -%} 20 | end options 21 | 22 | begin griddata 23 | icelltype{% if icelltype_layered %} layered{% endif %} 24 | {% for x in icelltype %} {{x}} 25 | {% endfor %} k{% if k_layered %} layered{% endif %} 26 | {% for x in k %} {{x}} 27 | {% endfor %}{% if k22 is defined %} k22{% if k22_layered %} layered{% endif %} 28 | {% for x in k22 %} {{x}} 29 | {% endfor -%} 30 | {% endif -%}{% if k33 is defined %} k33{% if k33_layered %} layered{% endif %} 31 | {% for x in k33 %} {{x}} 32 | {% endfor -%} 33 | {% endif -%}{% if angle1 is defined %} angle1{% if angle1_layered %} layered{% endif %} 34 | {% for x in angle1 %} {{x}} 35 | {% endfor -%} 36 | {% endif -%}{% if angle2 is defined %} angle2{% if angle2_layered %} layered{% endif %} 37 | {% for x in angle2 %} {{x}} 38 | {% endfor -%} 39 | {% endif -%}{% if angle3 is defined %} angle3{% if angle3_layered %} layered{% endif %} 40 | {% for x in angle3 %} {{x}} 41 | {% endfor -%} 42 | {% endif -%}{% if wetdry is defined %} wetdry{% if wetdry_layered %} layered{% endif %} 43 | {% for x in wetdry %} {{x}} 44 | {% endfor -%} 45 | {% endif -%} 46 | end griddata 47 | -------------------------------------------------------------------------------- /imod/templates/mf6/gwf-oc.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | {% if budget_file is defined %} budget fileout {{budget_file}} 3 | {% endif %} 4 | {%- if head_file is defined %} head fileout {{head_file}} 5 | {% endif %} 6 | {%- if concentration_file is defined %} concentration fileout {{concentration_file}} 7 | {% endif %} 8 | {%- if headprintrecord is defined %} head print_format columns {{columns}} width {{width}} digits {{digits}} {{format}} 9 | {% endif -%} 10 | end options 11 | 12 | {% for i, perioddict in periods.items() %}begin period {{i}} 13 | {% for key, value in perioddict.items() %} {{key}} {{value}} 14 | {% endfor -%} 15 | end period 16 | {% endfor %} -------------------------------------------------------------------------------- /imod/templates/mf6/gwf-rch.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | {% if fixed_cell%} fixed_cell 3 | {% endif %} 4 | {%- if auxiliary is defined %} auxiliary{% for aux in auxiliary %} {{aux}}{% endfor %} 5 | {% endif %} 6 | {%- if auxmultname is defined %} auxmultname {{auxmultname}} 7 | {% endif %} 8 | {%- if boundnames is defined %} boundnames 9 | {% endif %} 10 | {%- if print_input is defined %} print_input 11 | {% endif %} 12 | {%- if print_flows is defined %} print_flows 13 | {% endif %} 14 | {%- if save_flows is defined %} save_flows 15 | {% endif %} 16 | {%- if ts_filerecord is defined %} ts6 filein {{ts6_filename}} 17 | {% endif %} 18 | {%- if obs_filerecord is defined %} obs6 filein {{obs6_filename}} 19 | {% endif -%} 20 | end options 21 | 22 | begin dimensions 23 | maxbound {{maxbound}} 24 | end dimensions 25 | 26 | {% for i, path in periods.items() %}begin period {{i}} 27 | open/close {{path}}{% if binary %} (binary){% endif %} 28 | end period 29 | {% endfor %} -------------------------------------------------------------------------------- /imod/templates/mf6/gwf-rcha.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | readasarrays 3 | {%- if fixed_cell is defined -%} fixed_cell{%- endif -%} 4 | {%- if auxiliary is defined -%} auxiliary {{auxiliary(naux)}}{%- endif -%} 5 | {%- if auxmultname is defined -%} auxmultname {{auxmultname}}{%- endif -%} 6 | {%- if print_input is defined -%} print_input{%- endif -%} 7 | {%- if print_flows is defined -%} print_flows{%- endif -%} 8 | {%- if save_flows is defined -%} save_flows{%- endif -%} 9 | {%- if tas_filerecord is defined -%} tas6 filein {{tas6_filename}}{%- endif -%} 10 | {%- if obs_filerecord is defined -%} obs6 filein {{obs6_filename}}{%- endif -%} 11 | end options 12 | 13 | {% for i, path in periods.items() %}begin period {{i}} 14 | open/close {{path}}{% if binary %} (binary){% endif %} 15 | end period{% endfor %} -------------------------------------------------------------------------------- /imod/templates/mf6/gwf-riv.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | {% if auxiliary is defined %} auxiliary{% for aux in auxiliary %} {{aux}}{% endfor %} 3 | {% endif %} 4 | {%- if auxmultname is defined %} auxmultname {{auxmultname}} 5 | {% endif %} 6 | {%- if boundnames is defined %} boundnames 7 | {% endif %} 8 | {%- if print_input is defined %} print_input 9 | {% endif %} 10 | {%- if print_flows is defined %} print_flows 11 | {% endif %} 12 | {%- if save_flows is defined %} save_flows 13 | {% endif %} 14 | {%- if ts_filerecord is defined %} ts6 filein {{ts6_filename}} 15 | {% endif %} 16 | {%- if obs_filerecord is defined %} obs6 filein {{obs6_filename}} 17 | {% endif %} 18 | {%- if mover is defined %} mover 19 | {% endif -%} 20 | end options 21 | 22 | begin dimensions 23 | maxbound {{maxbound}} 24 | end dimensions 25 | 26 | {% for i, path in periods.items() %}begin period {{i}} 27 | open/close {{path}}{% if binary %} (binary){% endif %} 28 | end period 29 | {% endfor %} -------------------------------------------------------------------------------- /imod/templates/mf6/gwf-sfr.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | {%- if auxiliary is defined -%} auxiliary {{auxiliary(naux)}}{%- endif -%} 3 | {%- if boundnames is defined -%} boundnames{%- endif -%} 4 | {%- if print_input is defined -%} print_input{%- endif -%} 5 | {%- if print_stage is defined -%} print_stage{%- endif -%} 6 | {%- if print_flows is defined -%} print_flows{%- endif -%} 7 | {%- if save_flows is defined -%} save_flows{%- endif -%} 8 | {%- if stage_filerecord is defined -%} stage fileout {{stagefile}}{%- endif -%} 9 | {%- if budget_filerecord is defined -%} budget fileout {{budgetfile}}{%- endif -%} 10 | {%- if ts_filerecord is defined -%} ts6 filein {{ts6_filename}}{%- endif -%} 11 | {%- if obs_filerecord is defined -%} obs6 filein {{obs6_filename}}{%- endif -%} 12 | {%- if mover is defined -%} mover{%- endif -%} 13 | {%- if maximum_iterations is defined -%} maximum_iterations {{maximum_iterations}}{%- endif -%} 14 | {%- if maximum_depth_change is defined -%} maximum_depth_change {{maximum_depth_change}}{%- endif -%} 15 | {%- if unit_conversion is defined -%} unit_conversion {{unit_conversion}}{%- endif -%} 16 | end options 17 | 18 | begin dimensions 19 | nreaches {{nreaches}} 20 | end dimensions 21 | 22 | begin packagedata 23 | {{rno}} {{cellid(ncelldim)}} {{rlen}} {{rwid}} {{rgrd}} {{rtp}} {{rbth}} {{rhk}} {{man}} {{ncon}} {{ustrf}} {{ndv}} {%- if aux is defined -%}{{aux(naux)}}{%- endif -%} {%- if boundname is defined -%}{{boundname}}{%- endif -%} 24 | {{rno}} {{cellid(ncelldim)}} {{rlen}} {{rwid}} {{rgrd}} {{rtp}} {{rbth}} {{rhk}} {{man}} {{ncon}} {{ustrf}} {{ndv}} {%- if aux is defined -%}{{aux(naux)}}{%- endif -%} {%- if boundname is defined -%}{{boundname}}{%- endif -%} 25 | ... 26 | end packagedata 27 | 28 | begin connectiondata 29 | {{rno}} {{ic(ncon(rno))}} 30 | {{rno}} {{ic(ncon(rno))}} 31 | ... 32 | end connectiondata 33 | 34 | begin diversions 35 | {{rno}} {{idv}} {{iconr}} {{cprior}} 36 | {{rno}} {{idv}} {{iconr}} {{cprior}} 37 | ... 38 | end diversions 39 | 40 | {% for i, path in periods.items() %}begin period {{i}} 41 | open/close {{path}} (binary) 42 | end period{% endfor %} -------------------------------------------------------------------------------- /imod/templates/mf6/gwf-sto.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | {% if save_flows is defined %} save_flows 3 | {% endif %} 4 | {%- if storagecoefficient is defined %} storagecoefficient 5 | {% endif -%} 6 | end options 7 | 8 | begin griddata 9 | iconvert{% if iconvert_layered %} layered{% endif %} 10 | {% for x in iconvert %} {{x}} 11 | {% endfor %} ss{% if ss_layered %} layered{% endif %} 12 | {% for x in ss %} {{x}} 13 | {% endfor %} 14 | {%- if sy is defined %} sy{% if sy_layered %} layered{% endif %} 15 | {% for x in sy %} {{x}} 16 | {% endfor -%} 17 | {% endif -%} 18 | 19 | end griddata 20 | 21 | {% for i, value in periods.items() %}begin period {{i}} 22 | {% if value %}transient{% else %}steady-state{% endif %} 23 | end period 24 | {% endfor %} -------------------------------------------------------------------------------- /imod/templates/mf6/gwf-uzf.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | {%- if auxiliary is defined %} 3 | auxiliary {{auxiliary(naux)}}{% endif %} 4 | {%- if auxmultname is defined %} 5 | auxmultname {{auxmultname}}{% endif %} 6 | {%- if boundnames is defined %} 7 | boundnames{% endif %} 8 | {%- if print_input is defined %} 9 | print_input{% endif %} 10 | {%- if print_flows is defined %} 11 | print_flows{% endif %} 12 | {%- if save_flows is defined %} 13 | save_flows{% endif %} 14 | {%- if budget_fileout is defined %} 15 | budget fileout {{budget_fileout}}{% endif %} 16 | {%- if budgetcsv_fileout is defined %} 17 | budgetcsv fileout {{budgetcsv_fileout}}{% endif %} 18 | {%- if water_content_file is defined %} 19 | water_content fileout {{water_content_file}}{% endif %} 20 | {%- if ts_filerecord is defined %} 21 | ts6 filein {{ts6_filename}}{% endif %} 22 | {%- if obs_filerecord is defined %} 23 | obs6 filein {{obs6_filename}}{% endif %} 24 | {%- if mover is defined %} 25 | mover{% endif %} 26 | {%- if simulate_et is defined %} 27 | simulate_et{% endif %} 28 | {%- if linear_gwet is defined %} 29 | linear_gwet{% endif %} 30 | {%- if square_gwet is defined %} 31 | square_gwet{% endif %} 32 | {%- if simulate_gwseep is defined %} 33 | simulate_gwseep{% endif %} 34 | {%- if unsat_etwc is defined %} 35 | unsat_etwc{% endif %} 36 | {%- if unsat_etae is defined %} 37 | unsat_etae{% endif %} 38 | end options 39 | 40 | begin dimensions 41 | nuzfcells {{nuzfcells}} 42 | ntrailwaves {{ntrailwaves}} 43 | nwavesets {{nwavesets}} 44 | end dimensions 45 | 46 | begin packagedata 47 | open/close {{packagedata}} 48 | end packagedata 49 | 50 | {% for i, path in periods.items() %}begin period {{i}} 51 | open/close {{path}} 52 | end period 53 | 54 | {% endfor %} -------------------------------------------------------------------------------- /imod/templates/mf6/gwf-wel.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | {% if auxiliary is defined %} auxiliary{% for aux in auxiliary %} {{aux}}{% endfor %} 3 | {% endif %} 4 | {%- if auxmultname is defined %} auxmultname {{auxmultname}} 5 | {% endif %} 6 | {%- if boundnames is defined %} boundnames 7 | {% endif %} 8 | {%- if print_input is defined %} print_input 9 | {% endif %} 10 | {%- if print_flows is defined %} print_flows 11 | {% endif %} 12 | {%- if save_flows is defined %} save_flows 13 | {% endif %} 14 | {%- if auto_flow_reduce is defined %} auto_flow_reduce {{auto_flow_reduce}} 15 | {% endif %} 16 | {%- if ts_filerecord is defined %} ts6 filein {{ts6_filename}} 17 | {% endif %} 18 | {%- if obs_filerecord is defined %} obs6 filein {{obs6_filename}} 19 | {% endif %} 20 | {%- if mover is defined %} mover 21 | {% endif -%} 22 | end options 23 | 24 | begin dimensions 25 | {%- if maxbound == 0 %} 26 | maxbound 1 27 | {% else %} 28 | maxbound {{maxbound}} 29 | {% endif -%} 30 | end dimensions 31 | 32 | {% for i, path in periods.items() %}begin period {{i}} 33 | open/close {{path}}{% if binary %} (binary){% endif %} 34 | end period 35 | {% endfor %} -------------------------------------------------------------------------------- /imod/templates/mf6/gwt-adv.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | scheme {{scheme}} 3 | end options -------------------------------------------------------------------------------- /imod/templates/mf6/gwt-dsp.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | {% if xt3d_off is defined %} xt3d_off 3 | {% endif %} 4 | {%- if xt3d_rhs is defined %} xt3d_rhs 5 | {% endif -%} 6 | end options 7 | 8 | begin griddata 9 | diffc{% if diffc_layered %} layered{% endif %} 10 | {% for x in diffc %} {{x}} 11 | {% endfor %} alh{% if alh_layered %} layered{% endif %} 12 | {% for x in alh %} {{x}} 13 | {% endfor %} ath1{% if ath1_layered %} layered{% endif %} 14 | {% for x in ath1 %} {{x}} 15 | {% endfor %}{% if alv is defined %} alv{% if alv_layered %} layered{% endif %} 16 | {% for x in alv %} {{x}} 17 | {% endfor -%} 18 | {% endif -%}{% if ath2 is defined %} ath2{% if ath2_layered %} layered{% endif %} 19 | {% for x in ath2 %} {{x}} 20 | {% endfor -%} 21 | {% endif -%}{% if atv is defined %} atv{% if atv_layered %} layered{% endif %} 22 | {% for x in atv %} {{x}} 23 | {% endfor -%} 24 | {% endif -%} 25 | end griddata -------------------------------------------------------------------------------- /imod/templates/mf6/gwt-ist.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | {% if save_flows is defined %} save_flows 3 | {% endif %} 4 | {%- if budgetfile is defined %} BUDGET FILEOUT {{budgetfile}} 5 | {% endif -%} 6 | {%- if budgetcsvfile is defined %} BUDGETCSV FILEOUT {{budgetcsvfile}} 7 | {% endif -%} 8 | {%- if sorption is defined %} sorption 9 | {% endif -%} 10 | {%- if first_order_decay is defined %} first_order_decay 11 | {% endif -%} 12 | {%- if zero_order_decay is defined %} zero_order_decay 13 | {% endif -%} 14 | {%- if cimfile is defined %} CIM FILEOUT {{cimfile}} 15 | PRINT_FORMAT COLUMNS {{columns}} WIDTH {{width}} DIGITS {{digits}} {{format}} 16 | {% endif -%} 17 | end options 18 | 19 | begin griddata 20 | {% if cim is defined %} cim{% if cim_layered %} layered{% endif %} 21 | {% for x in cim %} {{x}} 22 | {% endfor -%}{% endif %} thetaim{% if thetaim_layered %} layered{% endif %} 23 | {% for x in thetaim %} {{x}} 24 | {% endfor %} zetaim{% if zetaim_layered %} layered{% endif %} 25 | {% for x in zetaim %} {{x}} 26 | {% endfor -%} 27 | {% if decay is defined %} decay{% if decay_layered %} layered{% endif %} 28 | {% for x in decay %} {{x}} 29 | {% endfor -%}{% endif -%} 30 | {% if decay_sorbed is defined %} decay_sorbed{% if decay_sorbed_layered %} layered{% endif %} 31 | {% for x in decay_sorbed %} {{x}} 32 | {% endfor -%}{% endif -%} 33 | {% if bulk_density is defined %} bulk_density{% if bulk_density_layered %} layered{% endif %} 34 | {% for x in bulk_density %} {{x}} 35 | {% endfor -%}{% endif -%} 36 | {% if distcoef is defined %} distcoef{% if distcoef_layered %} layered{% endif %} 37 | {% for x in distcoef %} {{x}} 38 | {% endfor -%}{% endif -%} 39 | end griddata -------------------------------------------------------------------------------- /imod/templates/mf6/gwt-mst.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | {%- if print_flows is defined %} print_flows{%- endif -%} 3 | {%- if first_order_decay is defined %} 4 | first_order_decay{% endif %} 5 | {%- if zero_order_decay is defined %} 6 | zero_order_decay{% endif %} 7 | {%- if sorption is defined %} 8 | sorption {{sorption}}{% endif %} 9 | end options 10 | 11 | begin griddata 12 | porosity{% if porosity_layered %} layered{% endif %} 13 | {% for x in porosity %} {{x}}{% endfor %}{%- if decay is defined %} 14 | decay{% if decay_layered %} layered{% endif %} 15 | {% for x in decay %} {{x}}{% endfor %} 16 | {%- endif %}{%- if decay_sorbed is defined %} 17 | decay_sorbed{% if decay_sorbed_layered %} layered{% endif %} 18 | {% for x in decay_sorbed %} {{x}}{% endfor %} 19 | {%- endif %}{%- if bulk_density is defined %} 20 | bulk_density{% if bulk_density_layered %} layered{% endif %} 21 | {% for x in bulk_density %} {{x}}{% endfor %} 22 | {%- endif %}{%- if distcoef is defined %} 23 | distcoef{% if distcoef_layered %} layered{% endif %} 24 | {% for x in distcoef %} {{x}}{% endfor %} 25 | {%- endif %}{%- if sp2 is defined %} 26 | sp2{% if sp2_layered %} layered{% endif %} 27 | {% for x in sp2 %} {{x}}{% endfor %} 28 | {%- endif %} 29 | end griddata -------------------------------------------------------------------------------- /imod/templates/mf6/gwt-nam.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | {% if listing_file is defined %} list {{listing_file}} 3 | {% endif %} 4 | {%- if print_input is defined %} print_input 5 | {% endif %} 6 | {%- if print_flows is defined %} print_flows 7 | {% endif %} 8 | {%- if save_flows is defined %} save_flows 9 | {% endif -%} 10 | end options 11 | 12 | begin packages 13 | {% for (ftype, fname, pkgname) in packages%} {{ftype}} {{fname}} {{pkgname}} 14 | {% endfor -%} 15 | end packages 16 | -------------------------------------------------------------------------------- /imod/templates/mf6/gwt-src.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | {%- if print_input is defined %} print_input 3 | {% endif %} 4 | {%- if print_flows is defined %} print_flows 5 | {% endif %} 6 | {%- if save_flows is defined %} save_flows 7 | {% endif %} 8 | {%- if ts_filerecord is defined %} ts6 filein {{ts6_filename}} 9 | {% endif %} 10 | {%- if obs_filerecord is defined %} obs6 filein {{obs6_filename}} 11 | {% endif %} 12 | end options 13 | 14 | begin dimensions 15 | maxbound {{maxbound}} 16 | end dimensions 17 | 18 | {% for i, path in periods.items() %}begin period {{i}} 19 | open/close {{path}}{% if binary %} (binary){% endif %} 20 | end period 21 | {% endfor %} -------------------------------------------------------------------------------- /imod/templates/mf6/gwt-ssm.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | {% if print_flows is defined %} print_flows 3 | {% endif %} 4 | {%- if save_flows is defined %} save_flows 5 | {% endif -%} 6 | end options 7 | 8 | begin sources 9 | {% for name, srctype, auxname in sources %} {{name}} {{srctype}} {{auxname}} 10 | {% endfor -%} 11 | end sources 12 | -------------------------------------------------------------------------------- /imod/templates/mf6/sim-nam.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | {% if continue is defined %} continue 3 | {% endif %} 4 | {%- if nocheck is defined %} nocheck 5 | {% endif %} 6 | {%- if memory_print_option is defined %} memory_print_option {{memory_print_option}} 7 | {% endif -%} 8 | end options 9 | 10 | begin timing 11 | tdis6 {{tdis6}} 12 | end timing 13 | 14 | begin models 15 | {% for mtype, mfname, name in models %} {{mtype}} {{mfname}} {{name}} 16 | {% endfor -%} 17 | end models 18 | 19 | begin exchanges 20 | {%- for exgtype, exgfile, exgmnamea, exgmnameb in exchanges %} 21 | {{exgtype}} {{exgfile}} {{exgmnamea}} {{exgmnameb}} 22 | {%- endfor %} 23 | 24 | end exchanges 25 | 26 | {% for solutiongroup in solutiongroups %}begin solutiongroup {{loop.index}} 27 | {% if mxiter is defined %} mxiter {{mxiter}} 28 | {% endif -%} 29 | {% for slntype, slnfname, slnmnames in solutiongroup %} {{slntype}} {{slnfname}} {{slnmnames|join(' ')}} 30 | {% endfor -%} 31 | end solutiongroup{% endfor %} 32 | -------------------------------------------------------------------------------- /imod/templates/mf6/sim-tdis.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | {% if time_units is defined %} time_units {{time_units}} 3 | {% endif %} 4 | {%- if start_date_time is defined %} start_date_time {{start_date_time}} 5 | {% endif -%} 6 | end options 7 | 8 | begin dimensions 9 | nper {{nper}} 10 | end dimensions 11 | 12 | begin perioddata 13 | {% for perlen, nstp, tsmult in perioddata %} {{perlen}} {{nstp}} {{tsmult}} 14 | {% endfor -%} 15 | end perioddata 16 | -------------------------------------------------------------------------------- /imod/templates/mf6/utl-lak-tab.j2: -------------------------------------------------------------------------------- 1 | begin dimensions 2 | nrow {{nrow}} 3 | ncol {{ncol}} 4 | end dimensions 5 | 6 | begin table 7 | {{stage}} {{volume}} {{sarea}} {%- if barea is defined -%}{{barea}}{%- endif -%} 8 | {{stage}} {{volume}} {{sarea}} {%- if barea is defined -%}{{barea}}{%- endif -%} 9 | ... 10 | end table 11 | -------------------------------------------------------------------------------- /imod/templates/mf6/utl-obs.j2: -------------------------------------------------------------------------------- 1 | begin options 2 | {%- if digits is defined -%} digits {{digits}}{%- endif -%} 3 | {%- if print_input is defined -%} print_input{%- endif -%} 4 | end options 5 | 6 | begin continuous 7 | {{obsname}} {{obstype}} {{id}} {%- if id2 is defined -%}{{id2}}{%- endif -%} 8 | {{obsname}} {{obstype}} {{id}} {%- if id2 is defined -%}{{id2}}{%- endif -%} 9 | ... 10 | end continuous 11 | -------------------------------------------------------------------------------- /imod/templates/mf6/utl-tas.j2: -------------------------------------------------------------------------------- 1 | begin attributes 2 | name {{time_series_nameany1d}} 3 | name 4 | {{time_series_nameany1d}} 5 | {%- if interpolation_methodrecord is defined -%} method {{interpolation_method}}{%- endif -%} 6 | method 7 | {{interpolation_method}} 8 | {%- if sfacrecord is defined -%} sfac {{sfacvaltime_series_name}}{%- endif -%} 9 | sfac 10 | {{sfacvaltime_series_name}} 11 | end attributes 12 | 13 | begin time 14 | 15 | {tas_array} 16 | end time 17 | -------------------------------------------------------------------------------- /imod/templates/mf6/utl-ts.j2: -------------------------------------------------------------------------------- 1 | begin attributes 2 | names {{time_series_namesany1d}} 3 | names 4 | {{time_series_namesany1d}} 5 | {%- if interpolation_methodrecord is defined -%} methods {{interpolation_methodtime_series_names}}{%- endif -%} 6 | methods 7 | {{interpolation_methodtime_series_names}} 8 | {%- if interpolation_methodrecord_single is defined -%} method {{interpolation_method_single}}{%- endif -%} 9 | method 10 | {{interpolation_method_single}} 11 | {%- if sfacrecord is defined -%} sfacs {{sfacval tuple[GridDataArray, GridDataArray]: 12 | x = [1.0, 2.0, 3.0] 13 | y = [1.0, 2.0, 3.0] 14 | time = pd.date_range(start="2000-01-01", end="2000-01-02", freq="D") 15 | 16 | dx = 1.0 17 | dy = -1.0 18 | # fmt: off 19 | precipitation = xr.DataArray( 20 | np.array( 21 | [ 22 | [[1.0, 1.0, 1.0], 23 | [nan, nan, nan], 24 | [1.0, 1.0, 1.0]], 25 | 26 | [[2.0, 2.0, 1.0], 27 | [nan, nan, nan], 28 | [1.0, 2.0, 1.0]], 29 | ] 30 | ), 31 | dims=("time", "y", "x"), 32 | coords={"time": time, "y": y, "x": x, "dx": dx, "dy": dy} 33 | ) 34 | 35 | evapotranspiration = xr.DataArray( 36 | np.array( 37 | [1.0, 3.0] 38 | ), 39 | dims=("time",), 40 | coords={"time": time} 41 | ) 42 | # fmt: on 43 | return precipitation, evapotranspiration 44 | -------------------------------------------------------------------------------- /imod/tests/fixtures/msw_regrid_fixture.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import xarray as xr 3 | 4 | nan = np.nan 5 | 6 | 7 | def get_3x3_area(): 8 | subunit = [0, 1] 9 | x = [1.0, 2.0, 3.0] 10 | y = [3.0, 2.0, 1.0] 11 | dx = 1.0 12 | dy = -1.0 13 | area = xr.DataArray( 14 | np.array( 15 | [ 16 | [[0.5, 0.5, 0.5], [nan, nan, nan], [1.0, 1.0, 1.0]], 17 | [[0.5, 0.5, 0.5], [1.0, 1.0, 1.0], [nan, nan, nan]], 18 | ] 19 | ), 20 | dims=("subunit", "y", "x"), 21 | coords={"subunit": subunit, "y": y, "x": x, "dx": dx, "dy": dy}, 22 | ) 23 | 24 | return area 25 | 26 | 27 | def get_5x5_new_grid(): 28 | x = [1.0, 1.5, 2.0, 2.5, 3.0] 29 | y = [3.0, 2.5, 2.0, 1.5, 1.0] 30 | dx = 0.5 31 | dy = -0.5 32 | layer = [1, 2, 3] 33 | 34 | idomain = xr.DataArray( 35 | 1, 36 | dims=("layer", "y", "x"), 37 | coords={"layer": layer, "y": y, "x": x, "dx": dx, "dy": dy}, 38 | ) 39 | return idomain 40 | -------------------------------------------------------------------------------- /imod/tests/test_code_checks.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import os 3 | from glob import glob 4 | 5 | 6 | def check_ast(node: ast.AST, path: str): 7 | ok = True 8 | if hasattr(node, "body"): 9 | for child in node.body: 10 | child_ok = check_ast(child, path) 11 | ok = ok and child_ok 12 | else: 13 | if isinstance(node, ast.Assert): 14 | print(f"assert detected in line {node.lineno} of {path}") 15 | ok = False 16 | if isinstance(node, ast.Expr) and isinstance(node.value, ast.Call): 17 | value = node.value 18 | if ( 19 | hasattr(value, "func") 20 | and hasattr(value.func, "id") 21 | and value.func.id == "print" 22 | ): 23 | print(f"print detected in line {node.lineno} of {path}") 24 | ok = False 25 | return ok 26 | 27 | 28 | def test_check_modules(): 29 | test_directory = os.path.realpath(os.path.dirname(os.path.abspath(__file__))) 30 | paths = glob(test_directory + "/../**/*.py") 31 | ok = True 32 | for path in paths: 33 | if ( 34 | test_directory in os.path.realpath(path) 35 | ): # if it's a test we don't care. this very file contains print statements itself. 36 | continue 37 | try: 38 | with open(path) as f: 39 | content = f.read() 40 | try: 41 | tree = ast.parse(content) 42 | module_ok = check_ast(tree, path) 43 | ok = ok and module_ok 44 | except Exception as e: 45 | print(f"parsing error in {path}, with error: {e}.") 46 | ok = False 47 | except Exception as e: 48 | print(f"error reading {path}, with error: {e}") 49 | ok = False 50 | assert ok 51 | -------------------------------------------------------------------------------- /imod/tests/test_common/test_utilities/test_mask_util.py: -------------------------------------------------------------------------------- 1 | import xarray as xr 2 | from pytest_cases import parametrize_with_cases 3 | from xarray.tests import raise_if_dask_computes 4 | 5 | from imod.common.utilities.mask import _skip_dataarray 6 | from imod.tests.fixtures.package_instance_creation import get_grid_da 7 | 8 | 9 | class DataArrayCases: 10 | def case_structured(self): 11 | grid = get_grid_da(False, float).chunk({"layer": 1}) 12 | return grid, False 13 | 14 | def case_unstructured(self): 15 | grid = get_grid_da(True, float).chunk({"layer": 1}) 16 | return grid, False 17 | 18 | def case_layered_constant(self): 19 | layered_constant = xr.DataArray( 20 | [1, 2, 3], coords={"layer": [1, 2, 3]}, dims=("layer",) 21 | ).chunk({"layer": 1}) 22 | return layered_constant, True 23 | 24 | def case_constant(self): 25 | return xr.DataArray(True).chunk({}), True 26 | 27 | 28 | @parametrize_with_cases("da, expected", cases=DataArrayCases) 29 | def test_skip_dataarray(da, expected): 30 | with raise_if_dask_computes(): 31 | assert _skip_dataarray(da) is expected 32 | -------------------------------------------------------------------------------- /imod/tests/test_couplers/test_metamod.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from imod.couplers import MetaMod 4 | 5 | 6 | def test_metamod_init(): 7 | # Functionality has been moved to primod, tests have been moved to 8 | # https://github.com/Deltares/imod_coupler/tree/main/tests/test_primod 9 | 10 | with pytest.raises(NotImplementedError): 11 | MetaMod() 12 | -------------------------------------------------------------------------------- /imod/tests/test_data.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | 4 | import xarray as xr 5 | 6 | import imod 7 | 8 | 9 | def test_twri_output(): 10 | dirpath = imod.util.temporary_directory() 11 | imod.data.twri_output(dirpath) 12 | contents = sorted(os.listdir(dirpath)) 13 | 14 | assert contents == [ 15 | "twri.cbc", 16 | "twri.grb", 17 | "twri.hds", 18 | ] 19 | 20 | 21 | def test_hondsrug_data(): 22 | for f in [ 23 | imod.data.hondsrug_initial, 24 | imod.data.hondsrug_layermodel, 25 | imod.data.hondsrug_meteorology, 26 | imod.data.hondsrug_river, 27 | imod.data.hondsrug_drainage, 28 | ]: 29 | assert isinstance(f(), xr.Dataset) 30 | 31 | 32 | def test_tutorial_03_data__unzipped(tmp_path): 33 | """ 34 | Test if tutorial 03 material can be unzipped. 35 | """ 36 | # Act 37 | prj_path = imod.data.tutorial_03(tmp_path) 38 | 39 | # Assert 40 | assert isinstance(prj_path, Path) 41 | assert prj_path.suffix == ".prj" 42 | n_files = sum(1 for x in prj_path.parent.rglob("*") if x.is_file()) 43 | assert n_files == 107 44 | 45 | 46 | def test_tutorial_03_data__open_data(tmp_path): 47 | """ 48 | Test if tutorial 03 material can be opened. 49 | """ 50 | # Act 51 | prj_path = imod.data.tutorial_03(tmp_path) 52 | imod5_data, _ = imod.formats.prj.open_projectfile_data(prj_path) 53 | 54 | # Assert 55 | expected_keys = { 56 | "bnd", 57 | "top", 58 | "bot", 59 | "khv", 60 | "kva", 61 | "sto", 62 | "shd", 63 | "rch", 64 | "riv", 65 | "drn-1", 66 | "drn-2", 67 | "drn-3", 68 | "drn-4", 69 | "pcg", 70 | } 71 | missing_keys = expected_keys - set(imod5_data.keys()) 72 | assert len(missing_keys) == 0 73 | -------------------------------------------------------------------------------- /imod/tests/test_examples.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import sys 4 | from glob import glob 5 | from pathlib import Path 6 | 7 | import pytest 8 | 9 | 10 | def get_examples(): 11 | # Where are we? --> __file__ 12 | # Move three up. 13 | path = Path(__file__).parent.parent.parent 14 | relpath = Path(os.path.relpath(path, os.getcwd())) / "examples/**/*.py" 15 | examples = [f for f in glob(str(relpath)) if f.endswith(".py")] 16 | return examples 17 | 18 | 19 | @pytest.mark.example 20 | @pytest.mark.parametrize("example", get_examples()) 21 | def test_example(example): 22 | result = subprocess.run([sys.executable, example], capture_output=True) 23 | if result.returncode != 0: 24 | raise RuntimeError( 25 | f"Example failed to run with returncode: {result.returncode} \n\n" 26 | f"stdout: {result.stdout.decode()} \n\n" 27 | f"stderr: {result.stderr.decode()} \n\n" 28 | ) 29 | -------------------------------------------------------------------------------- /imod/tests/test_mf6/test_ex01_twri_disv.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import numpy as np 4 | import pytest 5 | import xugrid as xu 6 | 7 | import imod 8 | 9 | 10 | @pytest.mark.skipif(sys.version_info < (3, 7), reason="capture_output added in 3.7") 11 | def test_simulation_write_and_run(twri_disv_model, tmp_path): 12 | simulation = twri_disv_model 13 | 14 | with pytest.raises( 15 | RuntimeError, match="Simulation ex01-twri-disv has not been written yet." 16 | ): 17 | twri_disv_model.run() 18 | 19 | modeldir = tmp_path / "ex01-twri-disv" 20 | simulation.write(modeldir, binary=False) 21 | simulation.run() 22 | 23 | head = imod.mf6.open_hds( 24 | modeldir / "GWF_1/GWF_1.hds", modeldir / "GWF_1/dis.disv.grb" 25 | ) 26 | assert isinstance(head, xu.UgridDataArray) 27 | assert head.dims == ("time", "layer", "mesh2d_nFaces") 28 | assert head.shape == (1, 3, 225) 29 | meanhead_layer = head.groupby("layer").mean(dim="mesh2d_nFaces") 30 | mean_answer = np.array([59.79181509, 30.44132373, 24.88576811]) 31 | assert np.allclose(meanhead_layer, mean_answer) 32 | 33 | 34 | @pytest.mark.skipif(sys.version_info < (3, 7), reason="capture_output added in 3.7") 35 | def test_slice_and_run(twri_disv_model, tmp_path): 36 | # TODO: bring back well once slicing is implemented... 37 | twri_disv_model["GWF_1"].pop("wel") 38 | simulation = twri_disv_model.clip_box( 39 | layer_min=1, 40 | layer_max=2, 41 | x_min=None, 42 | x_max=65_000.0, 43 | y_min=10_000.0, 44 | y_max=65_000.0, 45 | ) 46 | modeldir = tmp_path / "ex01-twri-disv-slice" 47 | simulation.write(modeldir, binary=True) 48 | simulation.run() 49 | -------------------------------------------------------------------------------- /imod/tests/test_mf6/test_mf6_adv.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | 3 | import numpy as np 4 | 5 | import imod 6 | 7 | 8 | def test_advection_upstream(): 9 | directory = pathlib.Path("mymodel") 10 | globaltimes = [np.datetime64("2000-01-01")] 11 | a = imod.mf6.AdvectionUpstream() 12 | actual = a.render(directory, "adv", globaltimes, True) 13 | expected = "begin options\n scheme upstream\nend options" 14 | assert actual == expected 15 | 16 | 17 | def test_advection_central(): 18 | directory = pathlib.Path("mymodel") 19 | globaltimes = [np.datetime64("2000-01-01")] 20 | a = imod.mf6.AdvectionCentral() 21 | actual = a.render(directory, "adv", globaltimes, True) 22 | expected = "begin options\n scheme central\nend options" 23 | assert actual == expected 24 | 25 | 26 | def test_advection_TVD(): 27 | directory = pathlib.Path("mymodel") 28 | globaltimes = [np.datetime64("2000-01-01")] 29 | a = imod.mf6.AdvectionTVD() 30 | actual = a.render(directory, "adv", globaltimes, True) 31 | expected = "begin options\n scheme TVD\nend options" 32 | assert actual == expected 33 | -------------------------------------------------------------------------------- /imod/tests/test_mf6/test_mf6_api_package.py: -------------------------------------------------------------------------------- 1 | import textwrap 2 | from pathlib import Path 3 | 4 | import imod 5 | 6 | 7 | def test_render(tmp_path: Path): 8 | api = imod.mf6.ApiPackage( 9 | maxbound=33, print_input=True, print_flows=True, save_flows=True 10 | ) 11 | actual = api.render(tmp_path, "api", [], True) 12 | 13 | expected = textwrap.dedent( 14 | """\ 15 | begin options 16 | print_input 17 | print_flows 18 | save_flows 19 | end options 20 | 21 | begin dimensions 22 | maxbound 33 23 | end dimensions""" 24 | ) 25 | assert actual == expected 26 | -------------------------------------------------------------------------------- /imod/tests/test_mf6/test_mf6_array_masking.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import xarray as xr 3 | 4 | from imod.common.utilities.mask import mask_arrays 5 | 6 | 7 | def test_array_masking(): 8 | x = [1] 9 | y = [1, 2, 3] 10 | layer = [1, 2] 11 | coords = {"layer": layer, "y": y, "x": x} 12 | dims = ("layer", "y", "x") 13 | 14 | array1 = xr.DataArray([[[1], [1], [1]], [[1], [1], [1]]], coords=coords, dims=dims) 15 | array2 = xr.DataArray( 16 | [[[np.nan], [1], [1]], [[1], [1], [1]]], coords=coords, dims=dims 17 | ) 18 | 19 | masked_arrays = mask_arrays({"array1": array1, "array2": array2}) 20 | 21 | # element 0,0,0 should be nan in both arrays 22 | assert np.isnan(masked_arrays["array1"].values[0, 0, 0]) 23 | assert np.isnan(masked_arrays["array2"].values[0, 0, 0]) 24 | 25 | # there should be only 1 nan in both arrays 26 | masked_arrays["array1"].values[0, 0, 0] = 1 27 | masked_arrays["array2"].values[0, 0, 0] = 1 28 | assert np.all(~np.isnan(masked_arrays["array1"].values)) 29 | assert np.all(~np.isnan(masked_arrays["array2"].values)) 30 | -------------------------------------------------------------------------------- /imod/tests/test_mf6/test_mf6_flopy_compatibility.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import flopy 4 | import pytest 5 | 6 | from imod.mf6 import Modflow6Simulation 7 | 8 | 9 | @pytest.mark.parametrize("absolute_paths", [True]) 10 | def test_readable_by_flopy( 11 | twri_model: Modflow6Simulation, tmp_path: Path, absolute_paths 12 | ): 13 | # write model to files using imod-python 14 | twri_model.write( 15 | directory=tmp_path, 16 | binary=False, 17 | validate=True, 18 | use_absolute_paths=absolute_paths, 19 | ) 20 | 21 | # import the model using flopy 22 | sim = flopy.mf6.MFSimulation.load(sim_ws=tmp_path) 23 | 24 | # run the simulation. 25 | flopy_sim_result = sim.run_simulation(silent=False, report=True) 26 | 27 | # If these 3 conditions were met the simulation ended with a "Normal termination of simulation" 28 | assert flopy_sim_result[0] is True 29 | assert "Normal termination of simulation." in flopy_sim_result[1][-1] 30 | assert len(flopy_sim_result) == 2 31 | 32 | 33 | @pytest.mark.parametrize("absolute_paths", [True, False]) 34 | def test_readable_by_mf6( 35 | twri_model: Modflow6Simulation, tmp_path: Path, absolute_paths 36 | ): 37 | # write model to files using imod-python 38 | twri_model.write( 39 | directory=tmp_path, 40 | binary=False, 41 | validate=True, 42 | use_absolute_paths=absolute_paths, 43 | ) 44 | 45 | # the run method will raise an exception if the run does not succeed 46 | twri_model.run() 47 | -------------------------------------------------------------------------------- /imod/tests/test_mf6/test_mf6_ic.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | import textwrap 3 | from copy import deepcopy 4 | 5 | import pytest 6 | 7 | import imod 8 | from imod.schemata import ValidationError 9 | 10 | 11 | def test_render(): 12 | ic_head = imod.mf6.InitialConditions(start=0.0) 13 | ic_start = imod.mf6.InitialConditions(start=0.0) 14 | directory = pathlib.Path("mymodel") 15 | actual_head = ic_head.render(directory, "ic", None, True) 16 | actual_start = ic_start.render(directory, "ic", None, True) 17 | expected = textwrap.dedent( 18 | """\ 19 | begin options 20 | end options 21 | 22 | begin griddata 23 | strt 24 | constant 0.0 25 | end griddata 26 | """ 27 | ) 28 | assert actual_head == expected 29 | assert actual_start == expected 30 | 31 | 32 | def test_wrong_dtype(): 33 | with pytest.raises(ValidationError): 34 | imod.mf6.InitialConditions(start=0) 35 | 36 | 37 | def test_validate_false(): 38 | imod.mf6.InitialConditions(start=0, validate=False) 39 | 40 | 41 | def test_wrong_arguments(): 42 | with pytest.raises(ValueError): 43 | imod.mf6.InitialConditions(head=0.0, start=1.0) 44 | 45 | 46 | def test_from_imod5(imod5_dataset, tmp_path): 47 | data = deepcopy(imod5_dataset[0]) 48 | 49 | target_grid = data["khv"]["kh"] 50 | 51 | ic = imod.mf6.InitialConditions.from_imod5_data(data, target_grid) 52 | 53 | ic._validate_init_schemata(True) 54 | 55 | rendered_ic = ic.render(tmp_path, "ic", None, False) 56 | assert "strt" in rendered_ic 57 | -------------------------------------------------------------------------------- /imod/tests/test_mf6/test_schemata.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import xarray as xr 3 | 4 | from imod import schemata as sch 5 | from imod.schemata import ValidationError 6 | 7 | 8 | def test_option_schema(): 9 | # Test for integer values 10 | schema = sch.OptionSchema([1, 2]) 11 | # None values should be skipped 12 | assert schema.validate(xr.DataArray(None)) is None 13 | assert schema.validate(xr.DataArray(1)) is None 14 | assert schema.validate(xr.DataArray(2)) is None 15 | 16 | with pytest.raises(ValidationError): 17 | schema.validate(xr.DataArray(3)) 18 | 19 | # Test for string values 20 | schema = sch.OptionSchema(["a", "b"]) 21 | # None values should be skipped 22 | assert schema.validate(xr.DataArray(None)) is None 23 | assert schema.validate(xr.DataArray("a")) is None 24 | # String values are treated case insensitive 25 | assert schema.validate(xr.DataArray("B")) is None 26 | 27 | with pytest.raises( 28 | ValidationError, match="Invalid option: c. Valid options are: a, b" 29 | ): 30 | schema.validate(xr.DataArray("c")) 31 | 32 | 33 | def test_AllCoordValues_schema(): 34 | schema = sch.AllCoordsValueSchema("layer", ">", 0) 35 | 36 | da = xr.DataArray([1, 1, 1], coords={"layer": [1, 2, 3]}, dims=("layer",)) 37 | assert schema.validate(da) is None 38 | assert schema.validate(da.rename(layer="ignore")) is None 39 | 40 | with pytest.raises( 41 | ValidationError, 42 | match="Not all values of coordinate layer comply with criterion: > 0", 43 | ): 44 | assert schema.validate(da.assign_coords(layer=[0, 1, 2])) 45 | -------------------------------------------------------------------------------- /imod/tests/test_mf6/test_utilities/test_grid.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from imod.common.utilities.grid import create_smallest_target_grid 4 | from imod.util.spatial import empty_2d 5 | 6 | 7 | def test_create_smallest_target_grid(): 8 | # Three grids with aligned cell edges at each 100m 9 | grid1 = empty_2d(dx=25.0, xmin=100.0, xmax=300.0, dy=-25.0, ymin=100.0, ymax=300.0) 10 | grid2 = empty_2d(dx=50.0, xmin=0.0, xmax=200.0, dy=-50.0, ymin=0.0, ymax=200.0) 11 | grid3 = empty_2d(dx=20.0, xmin=0.0, xmax=400.0, dy=-20.0, ymin=0.0, ymax=400.0) 12 | 13 | actual = create_smallest_target_grid(grid1, grid2, grid3) 14 | 15 | assert actual.coords["dx"] == 20.0 16 | assert actual.coords["dy"] == -20.0 17 | assert np.all(actual.coords["x"].values == [110.0, 130.0, 150.0, 170.0, 190.0]) 18 | assert np.all(actual.coords["y"].values == [190.0, 170.0, 150.0, 130.0, 110.0]) 19 | 20 | # Two grids with barely aligned cell edges. 21 | grid1 = empty_2d(dx=50.0, xmin=110.0, xmax=220.0, dy=-50.0, ymin=110.0, ymax=220.0) 22 | grid2 = empty_2d(dx=20.0, xmin=0.0, xmax=400.0, dy=-20.0, ymin=0.0, ymax=400.0) 23 | 24 | actual = create_smallest_target_grid(grid1, grid2) 25 | 26 | assert actual.coords["dx"] == 20.0 27 | assert actual.coords["dy"] == -20.0 28 | assert np.all(actual.coords["x"].values == [120.0, 140.0, 160.0, 180.0, 200.0]) 29 | assert np.all(actual.coords["y"].values == [210.0, 190.0, 170.0, 150.0, 130.0]) 30 | -------------------------------------------------------------------------------- /imod/tests/test_mf6/test_utilities/test_schemata_utilities.py: -------------------------------------------------------------------------------- 1 | from pytest_cases import parametrize_with_cases 2 | 3 | from imod.common.utilities.schemata import filter_schemata_dict 4 | from imod.mf6.riv import River 5 | from imod.schemata import AllNoDataSchema, IdentityNoDataSchema, IndexesSchema 6 | 7 | 8 | class CasesFilteredSchemata: 9 | def case_empty(self): 10 | schemata = {} 11 | arg = (AllNoDataSchema,) 12 | expected = {} 13 | return schemata, arg, expected 14 | 15 | def case_river_allnodata(self): 16 | schemata = River._write_schemata 17 | arg = (AllNoDataSchema,) 18 | expected = {"stage": [AllNoDataSchema()]} 19 | return schemata, arg, expected 20 | 21 | def case_river_allnodata_identitynodataschema(self): 22 | schemata = River._write_schemata 23 | arg = (AllNoDataSchema, IdentityNoDataSchema) 24 | expected = { 25 | "stage": [AllNoDataSchema()], 26 | "conductance": [IdentityNoDataSchema("stage")], 27 | "bottom_elevation": [IdentityNoDataSchema("stage")], 28 | "concentration": [IdentityNoDataSchema("stage")], 29 | } 30 | return schemata, arg, expected 31 | 32 | def case_river_not_found(self): 33 | # IndexesSchema part of _init_schemata, so should not be in 34 | # _write_schemata. 35 | schemata = River._write_schemata 36 | arg = (IndexesSchema,) 37 | expected = {} 38 | return schemata, arg, expected 39 | 40 | 41 | @parametrize_with_cases(("schemata", "arg", "expected"), cases=CasesFilteredSchemata) 42 | def test_filter_schemata_dict(schemata, arg, expected): 43 | # Act 44 | filtered_dict = filter_schemata_dict(schemata, arg) 45 | 46 | # Assert 47 | # Test if same variable names present in dicts 48 | assert filtered_dict.keys() == expected.keys() 49 | 50 | # Test if scheme types in list equal. 51 | for key in filtered_dict.keys(): 52 | schema_types = [type(s) for s in filtered_dict[key]] 53 | expected_types = [type(s) for s in expected[key]] 54 | assert schema_types == expected_types 55 | -------------------------------------------------------------------------------- /imod/tests/test_msw/test_annual_crop_factors.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import xarray as xr 3 | 4 | from imod.msw import AnnualCropFactors 5 | from imod.util.regrid import ( 6 | RegridderWeightsCache, 7 | ) 8 | 9 | 10 | def setup_cropfactors(): 11 | day_of_year = np.arange(1, 367) 12 | vegetation_index = np.arange(1, 4) 13 | 14 | coords = {"day_of_year": day_of_year, "vegetation_index": vegetation_index} 15 | soil_cover = xr.DataArray( 16 | data=np.zeros(day_of_year.shape + vegetation_index.shape), 17 | coords=coords, 18 | dims=("day_of_year", "vegetation_index"), 19 | ) 20 | soil_cover[132:254, :] = 1.0 21 | leaf_area_index = soil_cover * 3 22 | 23 | vegetation_factor = xr.zeros_like(soil_cover) 24 | vegetation_factor[132:142, :] = 0.7 25 | vegetation_factor[142:152, :] = 0.9 26 | vegetation_factor[152:162, :] = 1.0 27 | vegetation_factor[162:192, :] = 1.2 28 | vegetation_factor[192:244, :] = 1.1 29 | vegetation_factor[244:254, :] = 0.7 30 | 31 | cropfactors = AnnualCropFactors( 32 | soil_cover=soil_cover, 33 | leaf_area_index=leaf_area_index, 34 | interception_capacity=xr.zeros_like(soil_cover), 35 | vegetation_factor=vegetation_factor, 36 | interception_factor=xr.ones_like(soil_cover), 37 | bare_soil_factor=xr.ones_like(soil_cover), 38 | ponding_factor=xr.ones_like(soil_cover), 39 | ) 40 | return cropfactors 41 | 42 | 43 | def test_cropfactor_regrid(simple_2d_grid_with_subunits): 44 | crop_factors = setup_cropfactors() 45 | new_grid = simple_2d_grid_with_subunits 46 | 47 | regrid_context = RegridderWeightsCache() 48 | regridded = crop_factors.regrid_like(new_grid, regrid_context) 49 | 50 | assert regridded.dataset.equals(crop_factors.dataset) 51 | -------------------------------------------------------------------------------- /imod/tests/test_msw/test_idf_mapping.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import xarray as xr 3 | 4 | from imod.msw.idf_mapping import IdfMapping 5 | from imod.tests.fixtures.msw_regrid_fixture import get_3x3_area, get_5x5_new_grid 6 | from imod.util.regrid import RegridderWeightsCache 7 | 8 | 9 | def test_idf_mapping_regrid(): 10 | area = get_3x3_area() 11 | 12 | idf_mapping = IdfMapping(area, np.nan) 13 | 14 | new_grid = get_5x5_new_grid() 15 | regrid_context = RegridderWeightsCache() 16 | 17 | idf_mapping_regridded = idf_mapping.regrid_like(new_grid, regrid_context) 18 | 19 | assert len(idf_mapping_regridded.dataset["rows"]) == 5 20 | assert len(idf_mapping_regridded.dataset["columns"]) == 5 21 | assert np.isnan(idf_mapping_regridded.dataset["nodata"].values[()]) 22 | 23 | 24 | def test_idf_mapping_clip(): 25 | area = get_3x3_area() 26 | 27 | idf_mapping = IdfMapping(area, np.nan) 28 | 29 | idf_mapping_selected = idf_mapping.clip_box( 30 | x_min=1.0, x_max=2.5, y_min=1.0, y_max=2.5 31 | ) 32 | 33 | expected_area = area.sel(x=slice(1.0, 2.5), y=slice(2.5, 1.0)) 34 | xr.testing.assert_allclose(idf_mapping_selected.dataset["area"], expected_area) 35 | -------------------------------------------------------------------------------- /imod/tests/test_msw/test_utilities/test_parse.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from pytest_cases import parametrize_with_cases 3 | 4 | from imod.msw.utilities.parse import ( 5 | _try_parsing_string_to_number, 6 | correct_unsa_svat_path, 7 | ) 8 | 9 | 10 | class ParseCases: 11 | def case_int(self): 12 | return "1", int 13 | 14 | def case_float(self): 15 | return "1.0", float 16 | 17 | def case_string(self): 18 | return "a", str 19 | 20 | def case_aterisk(self): 21 | return "*", str 22 | 23 | def case_exclamation(self): 24 | return "!", str 25 | 26 | 27 | class UnsaSvatPathCases: 28 | def case_relpath(self): 29 | path = '"$unsat\\"' 30 | path_expected = "./unsat" 31 | return path, path_expected 32 | 33 | def case_abspath(self): 34 | path = '"C:\\Program Files\\MetaSWAP\\unsat\\"' 35 | path_expected = "C:\\Program Files\\MetaSWAP\\unsat" 36 | return path, path_expected 37 | 38 | def case_quoted(self): 39 | path = '"a/b/c"' 40 | path_expected = "a/b/c" 41 | return path, path_expected 42 | 43 | 44 | @parametrize_with_cases(["s", "expected_type"], cases=ParseCases) 45 | def test_try_parsing_string_to_value(s, expected_type): 46 | # Act 47 | parsed = _try_parsing_string_to_number(s) 48 | # Assert 49 | assert type(parsed) is expected_type 50 | 51 | 52 | @parametrize_with_cases(["path", "path_expected"], cases=UnsaSvatPathCases) 53 | def test_correct_unsa_svat_path(path, path_expected): 54 | # Act 55 | path_corrected = correct_unsa_svat_path(path) 56 | # Assert 57 | assert path_corrected == path_expected 58 | 59 | 60 | def test_correct_unsa_svat_path__wrong_type(): 61 | with pytest.raises(TypeError): 62 | correct_unsa_svat_path(1.0) 63 | 64 | with pytest.raises(TypeError): 65 | correct_unsa_svat_path(1) 66 | -------------------------------------------------------------------------------- /imod/tests/test_util/test_util.py: -------------------------------------------------------------------------------- 1 | def test_public_api(): 2 | """ 3 | Test if functions previously in imod.util.py are still available under same 4 | namespace 5 | """ 6 | from imod.util import ( 7 | cd, # noqa: F401 8 | empty_2d, # noqa: F401 9 | empty_2d_transient, # noqa: F401 10 | empty_3d, # noqa: F401 11 | empty_3d_transient, # noqa: F401 12 | from_mdal_compliant_ugrid2d, # noqa: F401 13 | ignore_warnings, # noqa: F401 14 | mdal_compliant_ugrid2d, # noqa: F401 15 | replace, # noqa: F401 16 | round_extent, # noqa: F401 17 | spatial_reference, # noqa: F401 18 | temporary_directory, # noqa: F401 19 | to_datetime, # noqa: F401 20 | to_ugrid2d, # noqa: F401 21 | transform, # noqa: F401 22 | ugrid2d_data, # noqa: F401 23 | values_within_range, # noqa: F401 24 | where, # noqa: F401 25 | ) 26 | -------------------------------------------------------------------------------- /imod/tests/test_util/test_util_context.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from imod.util.context import print_if_error 4 | 5 | 6 | def test_print_if_error(): 7 | with print_if_error(TypeError): 8 | 1 + "a" 9 | 10 | with pytest.raises(TypeError): 11 | with print_if_error(ValueError): 12 | 1 + "a" 13 | -------------------------------------------------------------------------------- /imod/tests/test_util/test_util_dims.py: -------------------------------------------------------------------------------- 1 | from imod.util.dims import enforced_dim_order 2 | 3 | 4 | def test_enforced_dim_order_structured(basic_dis): 5 | ibound, _, _ = basic_dis 6 | 7 | @enforced_dim_order 8 | def to_be_decorated(da): 9 | return da 10 | 11 | ibound_wrong_order = ibound.transpose("x", "y", "layer") 12 | 13 | actual = to_be_decorated(ibound_wrong_order) 14 | 15 | assert actual.dims == ibound.dims 16 | assert isinstance(actual, type(ibound)) 17 | 18 | 19 | def test_enforced_dim_order_unstructured(basic_unstructured_dis): 20 | ibound, _, _ = basic_unstructured_dis 21 | 22 | @enforced_dim_order 23 | def to_be_decorated(da): 24 | return da 25 | 26 | face_dim = ibound.ugrid.grid.face_dimension 27 | 28 | ibound_wrong_order = ibound.transpose(face_dim, "layer") 29 | 30 | actual = to_be_decorated(ibound_wrong_order) 31 | 32 | assert actual.dims == ibound.dims 33 | assert isinstance(actual, type(ibound)) 34 | -------------------------------------------------------------------------------- /imod/tests/test_visualize/test_visualize_cross_sections.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import numpy as np 3 | import pytest 4 | import xarray as xr 5 | 6 | import imod 7 | 8 | 9 | @pytest.fixture(scope="function") 10 | def testda(): 11 | def _testda(equidistant): 12 | if equidistant: 13 | coords = {"layer": [1, 2], "x": [1.0, 2.0, 3.0]} 14 | else: 15 | dx = np.arange(1.0, 4.0) 16 | x = dx.cumsum() - 0.5 * dx 17 | coords = {"layer": [1, 2], "x": x, "dx": ("x", dx)} 18 | 19 | dims = ("layer", "x") 20 | da = xr.DataArray(np.random.randn(2, 3), coords, dims) 21 | top = xr.full_like(da, 1.0) 22 | bottom = xr.full_like(da, 0.0) 23 | da = da.assign_coords(top=top) 24 | da = da.assign_coords(bottom=bottom) 25 | return da 26 | 27 | return _testda 28 | 29 | 30 | @pytest.mark.parametrize("equidistant", [True, False]) 31 | @pytest.mark.parametrize("transposed", [True, False]) 32 | @pytest.mark.parametrize("transpose_coords", [True, False]) 33 | def test_plot_cross_section(testda, equidistant, transposed, transpose_coords): 34 | da = testda(equidistant) 35 | if transposed: 36 | da = da.transpose("x", "layer", transpose_coords=transpose_coords) 37 | levels = [0.0, 0.25, 0.50, 0.75, 1.0] 38 | colors = ["#ffffcc", "#c7e9b4", "#7fcdbb", "#41b6c4", "#2c7fb8", "#253494"] 39 | fig, ax = imod.visualize.cross_section( 40 | da, layers=True, levels=levels, colors=colors 41 | ) 42 | assert isinstance(fig, plt.Figure) 43 | assert isinstance(ax, plt.Axes) 44 | -------------------------------------------------------------------------------- /imod/tests/test_visualize/test_visualize_waterbalance.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import numpy as np 3 | import pandas as pd 4 | import pytest 5 | 6 | import imod 7 | 8 | 9 | @pytest.fixture(scope="module") 10 | def waterbalance_df(): 11 | df = pd.DataFrame( 12 | np.random.rand(10, 6) * 100.0, 13 | index=pd.date_range("2001-01-01", "2001-01-10"), 14 | columns=["P", "ET", "Seepage", "Riv", "Wel", "Drainage"], 15 | ) 16 | return df 17 | 18 | 19 | def test_waterbalance_barchart(waterbalance_df): 20 | df = waterbalance_df 21 | inflows = ["P", "Riv", "Seepage"] 22 | outflows = ["ET", "Wel", "Drainage"] 23 | 24 | # Without optional arguments 25 | imod.visualize.waterbalance_barchart(df=df, inflows=inflows, outflows=outflows) 26 | 27 | # With optional arguments 28 | _, ax = plt.subplots() 29 | df["date"] = df.index 30 | colors = ["#b2182b", "#ef8a62", "#fddbc7", "#d1e5f0", "#67a9cf", "#2166ac"] 31 | kwargs = { 32 | "df": df, 33 | "inflows": inflows, 34 | "outflows": outflows, 35 | "datecolumn": "date", 36 | "format": "%Y-%m-%d", 37 | "ax": ax, 38 | "unit": "m3/d", 39 | "colors": colors, 40 | } 41 | imod.visualize.waterbalance_barchart(**kwargs) 42 | 43 | # Errors 44 | with pytest.raises(ValueError): 45 | faulty_kwargs = kwargs.copy() 46 | faulty_kwargs["datecolumn"] = "datetime" 47 | imod.visualize.waterbalance_barchart(**faulty_kwargs) 48 | with pytest.raises(ValueError): 49 | faulty_kwargs = kwargs.copy() 50 | faulty_kwargs["inflows"] = ["Precip", "ET", "Seepage"] 51 | imod.visualize.waterbalance_barchart(**faulty_kwargs) 52 | with pytest.raises(ValueError): 53 | faulty_kwargs = kwargs.copy() 54 | faulty_kwargs["colors"] = colors[:-1] 55 | imod.visualize.waterbalance_barchart(**faulty_kwargs) 56 | -------------------------------------------------------------------------------- /imod/tests/test_wq/test_wq_adv.py: -------------------------------------------------------------------------------- 1 | from imod.wq import AdvectionFiniteDifference, AdvectionModifiedMOC, AdvectionTVD 2 | 3 | 4 | def test_render__DF(): 5 | adv = AdvectionFiniteDifference(courant=1.0, weighting="upstream") 6 | out = adv._render() 7 | compare = "[adv]\n mixelm = 0\n percel = 1.0\n nadvfd = 0\n" 8 | assert out == compare 9 | 10 | adv = AdvectionFiniteDifference(courant=0.5, weighting="central") 11 | out = adv._render() 12 | compare = "[adv]\n mixelm = 0\n percel = 0.5\n nadvfd = 1\n" 13 | assert out == compare 14 | 15 | 16 | def test_render__TVD(): 17 | adv = AdvectionTVD(courant=1.0) 18 | out = adv._render() 19 | compare = "[adv]\n mixelm = -1\n percel = 1.0\n" 20 | assert out == compare 21 | 22 | 23 | def test_render__ModifiedMOC(): 24 | adv = AdvectionModifiedMOC() 25 | out = adv._render() 26 | compare = ( 27 | "[adv]\n" 28 | " mixelm = 2\n" 29 | " percel = 1.0\n" 30 | " itrack = 3\n" 31 | " wd = 0.5\n" 32 | " interp = 1\n" 33 | " nlsink = 2\n" 34 | " npsink = 40\n" 35 | ) 36 | assert out == compare 37 | -------------------------------------------------------------------------------- /imod/tests/test_wq/test_wq_chd.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | 3 | import numpy as np 4 | import pandas as pd 5 | import pytest 6 | import xarray as xr 7 | 8 | from imod.wq import ConstantHead 9 | 10 | 11 | @pytest.fixture(scope="function") 12 | def constanthead(): 13 | layer = np.arange(1, 4) 14 | y = np.arange(4.5, 0.0, -1.0) 15 | x = np.arange(0.5, 5.0, 1.0) 16 | head = xr.DataArray( 17 | np.full((3, 5, 5), 1.0), 18 | coords={"layer": layer, "y": y, "x": x, "dx": 1.0, "dy": -1.0}, 19 | dims=("layer", "y", "x"), 20 | ) 21 | 22 | chd = ConstantHead(head_start=head, head_end=head.copy(), concentration=head.copy()) 23 | return chd 24 | 25 | 26 | def test_render(constanthead): 27 | chd = constanthead 28 | directory = pathlib.Path(".") 29 | 30 | compare = """ 31 | shead_p?_s1_l$ = head_start_l$.idf 32 | ehead_p?_s1_l$ = head_end_l$.idf""" 33 | 34 | assert ( 35 | chd._render(directory, globaltimes=["?"], system_index=1, nlayer=3) == compare 36 | ) 37 | 38 | 39 | @pytest.mark.parametrize("varname", ["head_start", "head_end"]) 40 | def test_render__stress_repeats(constanthead, varname): 41 | chd = constanthead 42 | directory = pathlib.Path(".") 43 | da = chd[varname] 44 | datetimes = pd.date_range("2000-01-01", "2000-01-03") 45 | da_transient = xr.concat( 46 | [da.assign_coords(time=t) for t in datetimes[:-1]], dim="time" 47 | ) 48 | chd[varname] = da_transient 49 | 50 | stress_repeats = {datetimes[-1]: datetimes[0]} 51 | chd.repeat_stress(**{varname: stress_repeats}) 52 | _ = chd._render(directory, globaltimes=datetimes, system_index=1, nlayer=3) 53 | -------------------------------------------------------------------------------- /imod/tests/test_wq/test_wq_dis.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import pytest 4 | import xarray as xr 5 | 6 | from imod.wq import TimeDiscretization 7 | 8 | 9 | @pytest.fixture(scope="module") 10 | def discret(): 11 | datetimes = pd.date_range("2000-01-01", "2000-01-05") 12 | timestep_duration = xr.DataArray( 13 | np.full(5, 1.0), coords={"time": datetimes}, dims=("time",) 14 | ) 15 | 16 | dis = TimeDiscretization( 17 | timestep_duration=timestep_duration, 18 | n_timesteps=xr.full_like(timestep_duration, 1, dtype=np.int32), 19 | transient=xr.full_like(timestep_duration, True), 20 | timestep_multiplier=xr.full_like(timestep_duration, 1.0), 21 | max_n_transport_timestep=xr.full_like(timestep_duration, 10, dtype=np.int32), 22 | transport_timestep_multiplier=xr.full_like(timestep_duration, 1.0), 23 | transport_initial_timestep=0, 24 | ) 25 | return dis 26 | 27 | 28 | def test_render_dis(discret): 29 | dis = discret 30 | globaltimes = dis.dataset["time"].values 31 | 32 | compare = """ 33 | nper = 5 34 | perlen_p1:5 = 1.0 35 | nstp_p1:5 = 1 36 | sstr_p1:5 = tr 37 | tsmult_p1:5 = 1.0""" 38 | 39 | assert dis._render(globaltimes) == compare 40 | 41 | 42 | def test_render_dis__notime(discret): 43 | dis = TimeDiscretization(**discret.dataset.isel(time=0).drop_vars("time")) 44 | globaltimes = ["?"] 45 | 46 | compare = """ 47 | nper = 1 48 | perlen_p? = 1.0 49 | nstp_p? = 1 50 | sstr_p? = tr 51 | tsmult_p? = 1.0""" 52 | 53 | assert dis._render(globaltimes) == compare 54 | 55 | 56 | def test_render_btn(discret): 57 | dis = TimeDiscretization(**discret.dataset.isel(time=0).drop_vars("time")) 58 | globaltimes = ["?"] 59 | 60 | compare = """ 61 | tsmult_p? = 1.0 62 | dt0_p? = 0 63 | ttsmult_p? = 1.0 64 | mxstrn_p? = 10""" 65 | 66 | assert dis._render_btn(globaltimes) == compare 67 | -------------------------------------------------------------------------------- /imod/tests/test_wq/test_wq_dsp.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | import textwrap 3 | 4 | import numpy as np 5 | import pytest 6 | import xarray as xr 7 | 8 | from imod.wq import Dispersion 9 | 10 | 11 | @pytest.fixture(scope="module") 12 | def dispersion(): 13 | layer = np.arange(1, 4) 14 | y = np.arange(4.5, 0.0, -1.0) 15 | x = np.arange(0.5, 5.0, 1.0) 16 | longitudinal = xr.DataArray( 17 | np.full((3, 5, 5), 1.0), 18 | coords={"layer": layer, "y": y, "x": x, "dx": 1.0, "dy": -1.0}, 19 | dims=("layer", "y", "x"), 20 | ) 21 | 22 | dsp = Dispersion( 23 | longitudinal=longitudinal, 24 | ratio_horizontal=longitudinal.copy(), 25 | ratio_vertical=longitudinal.copy(), 26 | diffusion_coefficient=longitudinal.copy(), 27 | ) 28 | return dsp 29 | 30 | 31 | def test_render_idf(dispersion): 32 | dsp = dispersion 33 | directory = pathlib.Path(".") 34 | 35 | compare = textwrap.dedent( 36 | """\ 37 | [dsp] 38 | al_l$ = longitudinal_l$.idf 39 | trpt_l$ = ratio_horizontal_l$.idf 40 | trpv_l$ = ratio_vertical_l$.idf 41 | dmcoef_l$ = diffusion_coefficient_l$.idf""" 42 | ) 43 | 44 | assert dsp._render(directory, nlayer=3) == compare 45 | 46 | 47 | def test_render_constant(dispersion): 48 | dsp = Dispersion(1.0, 1.0, 1.0, 1.0) 49 | directory = pathlib.Path(".") 50 | 51 | compare = textwrap.dedent( 52 | """\ 53 | [dsp] 54 | al_l? = 1.0 55 | trpt_l? = 1.0 56 | trpv_l? = 1.0 57 | dmcoef_l? = 1.0""" 58 | ) 59 | 60 | assert dsp._render(directory, nlayer=4) == compare 61 | 62 | 63 | def test_render_constant_per_layer(dispersion): 64 | dsp = dispersion 65 | # Get rid of x and y, so it's no longer an idf 66 | dsp = Dispersion(**dsp.dataset.isel(x=0, y=0).drop_vars(["y", "x", "dx", "dy"])) 67 | directory = pathlib.Path(".") 68 | 69 | compare = textwrap.dedent( 70 | """\ 71 | [dsp] 72 | al_l1:3 = 1.0 73 | trpt_l1:3 = 1.0 74 | trpv_l1:3 = 1.0 75 | dmcoef_l1:3 = 1.0""" 76 | ) 77 | 78 | assert dsp._render(directory, nlayer=3) == compare 79 | -------------------------------------------------------------------------------- /imod/tests/test_wq/test_wq_ghb.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | 3 | import numpy as np 4 | import pandas as pd 5 | import pytest 6 | import xarray as xr 7 | 8 | from imod.wq import GeneralHeadBoundary 9 | 10 | 11 | @pytest.fixture(scope="function") 12 | def headboundary(): 13 | layer = np.arange(1, 4) 14 | y = np.arange(4.5, 0.0, -1.0) 15 | x = np.arange(0.5, 5.0, 1.0) 16 | head = xr.DataArray( 17 | np.full((3, 5, 5), 1.0), 18 | coords={"layer": layer, "y": y, "x": x, "dx": 1.0, "dy": -1.0}, 19 | dims=("layer", "y", "x"), 20 | ) 21 | 22 | ghb = GeneralHeadBoundary( 23 | head=head, 24 | conductance=head.copy(), 25 | concentration=head.copy(), 26 | density=head.copy(), 27 | ) 28 | return ghb 29 | 30 | 31 | def test_render(headboundary): 32 | ghb = headboundary 33 | directory = pathlib.Path(".") 34 | 35 | compare = """ 36 | bhead_p?_s1_l$ = head_l$.idf 37 | cond_p?_s1_l$ = conductance_l$.idf 38 | ghbssmdens_p?_s1_l$ = density_l$.idf""" 39 | 40 | assert ( 41 | ghb._render(directory, globaltimes=["?"], system_index=1, nlayer=3) == compare 42 | ) 43 | 44 | 45 | @pytest.mark.parametrize("varname", ["head", "conductance", "concentration", "density"]) 46 | def test_render__stress_repeats(headboundary, varname): 47 | ghb = headboundary 48 | directory = pathlib.Path(".") 49 | da = ghb[varname] 50 | datetimes = pd.date_range("2000-01-01", "2000-01-03") 51 | da_transient = xr.concat( 52 | [da.assign_coords(time=t) for t in datetimes[:-1]], dim="time" 53 | ) 54 | ghb[varname] = da_transient 55 | 56 | stress_repeats = {datetimes[-1]: datetimes[0]} 57 | ghb.repeat_stress(**{varname: stress_repeats}) 58 | _ = ghb._render(directory, globaltimes=datetimes, system_index=1, nlayer=3) 59 | # TODO check result 60 | -------------------------------------------------------------------------------- /imod/tests/test_wq/test_wq_lpf.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | import textwrap 3 | 4 | import numpy as np 5 | import pytest 6 | import xarray as xr 7 | 8 | from imod.wq import LayerPropertyFlow 9 | 10 | 11 | @pytest.fixture(scope="module") 12 | def layerpropertyflow(): 13 | def create_lpf(bed=False): 14 | layer = np.arange(1, 4) 15 | y = np.arange(4.5, 0.0, -1.0) 16 | x = np.arange(0.5, 5.0, 1.0) 17 | k_horizontal = xr.DataArray( 18 | np.full((3, 5, 5), 1.0), 19 | coords={"layer": layer, "y": y, "x": x, "dx": 1.0, "dy": -1.0}, 20 | dims=("layer", "y", "x"), 21 | ) 22 | 23 | lpf = LayerPropertyFlow( 24 | k_horizontal=k_horizontal, 25 | k_vertical=k_horizontal.copy(), 26 | horizontal_anisotropy=k_horizontal.copy(), 27 | interblock=k_horizontal.copy(), 28 | layer_type=k_horizontal.copy(), 29 | specific_storage=k_horizontal.copy(), 30 | specific_yield=k_horizontal.copy(), 31 | save_budget=False, 32 | layer_wet=k_horizontal.copy(), 33 | interval_wet=0.01, 34 | method_wet="wetfactor", 35 | head_dry=1.0e20, 36 | ) 37 | return lpf 38 | 39 | return create_lpf 40 | 41 | 42 | def test_render(layerpropertyflow): 43 | create_lpf = layerpropertyflow 44 | lpf = create_lpf(bed=False) 45 | directory = pathlib.Path(".") 46 | 47 | compare = textwrap.dedent( 48 | """\ 49 | [lpf] 50 | ilpfcb = 0 51 | hdry = 1e+20 52 | layvka_l? = 0 53 | laytyp_l$ = layer_type_l$.idf 54 | layavg_l$ = interblock_l$.idf 55 | chani_l$ = horizontal_anisotropy_l$.idf 56 | hk_l$ = k_horizontal_l$.idf 57 | vka_l$ = k_vertical_l$.idf 58 | ss_l$ = specific_storage_l$.idf 59 | sy_l$ = specific_yield_l$.idf 60 | laywet_l$ = layer_wet_l$.idf""" 61 | ) 62 | 63 | assert lpf._render(directory, nlayer=3) == compare 64 | -------------------------------------------------------------------------------- /imod/tests/test_wq/test_wq_mal.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import xarray as xr 4 | 5 | from imod.wq import MassLoading 6 | 7 | 8 | def test_render_mal__scalar(): 9 | mal = MassLoading(concentration=1.0) 10 | actual = mal._render_ssm(".", ["?"], nlayer=3) 11 | compare = "\n cmal_t1_p?_l? = 1.0" 12 | assert actual == compare 13 | 14 | 15 | def test_render_mal__array(): 16 | time = pd.date_range("2000-01-01", "2000-01-03", freq="D") 17 | conc = xr.DataArray( 18 | np.ones((3, 5, 3, 4)), 19 | { 20 | "time": time, 21 | "layer": [1, 2, 3, 4, 5], 22 | "y": [0.5, 1.5, 2.5], 23 | "x": [0.5, 1.5, 2.5, 3.5], 24 | }, 25 | dims=("time", "layer", "y", "x"), 26 | ) 27 | mal = MassLoading(concentration=conc) 28 | actual = mal._render_ssm("mal", globaltimes=time, nlayer=5) 29 | compare = """ 30 | cmal_t1_p1_l$ = mal/concentration_20000101000000_l$.idf 31 | cmal_t1_p2_l$ = mal/concentration_20000102000000_l$.idf 32 | cmal_t1_p3_l$ = mal/concentration_20000103000000_l$.idf""" 33 | assert actual == compare 34 | -------------------------------------------------------------------------------- /imod/tests/test_wq/test_wq_oc.py: -------------------------------------------------------------------------------- 1 | import textwrap 2 | 3 | from imod.wq import OutputControl 4 | 5 | 6 | def test_render(): 7 | oc = OutputControl() 8 | 9 | compare = textwrap.dedent( 10 | """\ 11 | [oc] 12 | savehead_p?_l? = False 13 | saveconclayer_p?_l? = False 14 | savebudget_p?_l? = False 15 | saveheadtec_p?_l? = False 16 | saveconctec_p?_l? = False 17 | savevxtec_p?_l? = False 18 | savevytec_p?_l? = False 19 | savevztec_p?_l? = False 20 | saveheadvtk_p?_l? = False 21 | saveconcvtk_p?_l? = False 22 | savevelovtk_p?_l? = False""" 23 | ) 24 | 25 | assert oc._render() == compare 26 | -------------------------------------------------------------------------------- /imod/tests/test_wq/test_wq_tvc.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import xarray as xr 4 | 5 | from imod.wq import TimeVaryingConstantConcentration 6 | 7 | 8 | def test_render_tvc__scalar(): 9 | tvc = TimeVaryingConstantConcentration(concentration=1.0) 10 | actual = tvc._render_ssm(".", ["?"], nlayer=3) 11 | compare = "\n ctvc_t1_p?_l? = 1.0" 12 | assert actual == compare 13 | 14 | 15 | def test_render_tvc__array(): 16 | time = pd.date_range("2000-01-01", "2000-01-03", freq="D") 17 | conc = xr.DataArray( 18 | np.ones((3, 5, 3, 4)), 19 | { 20 | "time": time, 21 | "layer": [1, 2, 3, 4, 5], 22 | "y": [0.5, 1.5, 2.5], 23 | "x": [0.5, 1.5, 2.5, 3.5], 24 | }, 25 | dims=("time", "layer", "y", "x"), 26 | ) 27 | tvc = TimeVaryingConstantConcentration(concentration=conc) 28 | actual = tvc._render_ssm("tvc", globaltimes=time, nlayer=5) 29 | compare = """ 30 | ctvc_t1_p1_l$ = tvc/concentration_20000101000000_l$.idf 31 | ctvc_t1_p2_l$ = tvc/concentration_20000102000000_l$.idf 32 | ctvc_t1_p3_l$ = tvc/concentration_20000103000000_l$.idf""" 33 | assert actual == compare 34 | -------------------------------------------------------------------------------- /imod/tests/test_wq/test_wq_vdf.py: -------------------------------------------------------------------------------- 1 | import textwrap 2 | 3 | from imod.wq import VariableDensityFlow 4 | 5 | 6 | def test_render(): 7 | vdf = VariableDensityFlow( 8 | density_species=1, 9 | density_min=1000.0, 10 | density_max=1025.0, 11 | density_ref=1000.0, 12 | density_concentration_slope=0.71, 13 | density_criterion=0.01, 14 | read_density=False, 15 | internodal="central", 16 | coupling=1, 17 | correct_water_table=False, 18 | ) 19 | 20 | compare = textwrap.dedent( 21 | """\ 22 | [vdf] 23 | mtdnconc = 1 24 | densemin = 1000.0 25 | densemax = 1025.0 26 | denseref = 1000.0 27 | denseslp = 0.71 28 | dnscrit = 0.01 29 | nswtcpl = 1 30 | iwtable = 0 31 | mfnadvfd = 2 32 | """ 33 | ) 34 | 35 | assert vdf._render() == compare 36 | -------------------------------------------------------------------------------- /imod/typing/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module to define type aliases. 3 | """ 4 | 5 | from datetime import datetime 6 | from typing import TYPE_CHECKING, Literal, TypeAlias, TypedDict, TypeVar, Union 7 | 8 | import numpy as np 9 | import xarray as xr 10 | import xugrid as xu 11 | from numpy.typing import NDArray 12 | 13 | GridDataArray: TypeAlias = Union[xr.DataArray, xu.UgridDataArray] 14 | GridDataset: TypeAlias = Union[xr.Dataset, xu.UgridDataset] 15 | GridDataDict: TypeAlias = dict[str, GridDataArray] 16 | ScalarAsDataArray: TypeAlias = Union[xr.DataArray, xu.UgridDataArray] 17 | ScalarAsDataset: TypeAlias = Union[xr.Dataset, xu.UgridDataset] 18 | UnstructuredData: TypeAlias = Union[xu.UgridDataset, xu.UgridDataArray] 19 | FloatArray: TypeAlias = NDArray[np.floating] 20 | IntArray: TypeAlias = NDArray[np.int_] 21 | StressPeriodTimesType: TypeAlias = list[datetime] | Literal["steady-state"] 22 | 23 | 24 | class SelSettingsType(TypedDict, total=False): 25 | layer: int 26 | drop: bool 27 | missing_dims: Literal["raise", "warn", "ignore"] 28 | 29 | 30 | class DropVarsType(TypedDict, total=False): 31 | names: str 32 | errors: Literal["raise", "ignore"] 33 | 34 | 35 | class Imod5DataDict(TypedDict, total=False): 36 | cap: GridDataDict 37 | extra: dict[str, list[str]] 38 | 39 | 40 | # Types for optional dependencies. 41 | if TYPE_CHECKING: 42 | import geopandas as gpd 43 | import shapely 44 | 45 | GeoDataFrameType: TypeAlias = gpd.GeoDataFrame 46 | GeoSeriesType: TypeAlias = gpd.GeoSeries 47 | PolygonType: TypeAlias = shapely.Polygon 48 | LineStringType: TypeAlias = shapely.LineString 49 | else: 50 | GeoDataFrameType = TypeVar("GeoDataFrameType") 51 | GeoSeriesType = TypeVar("GeoSeriesType") 52 | PolygonType = TypeVar("PolygonType") 53 | LineStringType = TypeVar("LineStringType") 54 | -------------------------------------------------------------------------------- /imod/util/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Miscellaneous Utilities. 3 | 4 | The utilies imported in this file are public API, and previously placed in 5 | imod/util.py. Therefore these should be available under the imod.util namespace. 6 | """ 7 | 8 | import warnings 9 | 10 | from imod.util.context import cd, ignore_warnings, print_if_error 11 | from imod.util.path import temporary_directory 12 | from imod.util.regrid import RegridderType, RegridderWeightsCache 13 | from imod.util.spatial import ( 14 | coord_reference, 15 | empty_2d, 16 | empty_2d_transient, 17 | empty_3d, 18 | empty_3d_transient, 19 | from_mdal_compliant_ugrid2d, 20 | get_cell_area, 21 | mdal_compliant_ugrid2d, 22 | spatial_reference, 23 | to_ugrid2d, 24 | transform, 25 | ugrid2d_data, 26 | ) 27 | from imod.util.structured import replace, values_within_range, where 28 | from imod.util.time import to_datetime 29 | 30 | 31 | def round_extent(extent, cellsize): 32 | """ 33 | This function is to preserve the imod.util.round_extent() namespace. Please 34 | refer to the new location in the future: imod.prepare.spatial.roundextent. 35 | """ 36 | # Import locally to avoid circular imports 37 | from imod.prepare.spatial import round_extent 38 | 39 | warnings.warn( 40 | "Use of `imod.util.round_extent` is deprecated, please use the new " 41 | "location `imod.prepare.spatial.round_extent`", 42 | DeprecationWarning, 43 | ) 44 | return round_extent(extent, cellsize) 45 | -------------------------------------------------------------------------------- /imod/util/context.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import os 3 | import pathlib 4 | import warnings 5 | from typing import Union 6 | 7 | 8 | @contextlib.contextmanager 9 | def ignore_warnings(): 10 | """ 11 | Contextmanager to ignore RuntimeWarnings as they are frequently 12 | raised by the Dask delayed scheduler. 13 | 14 | Examples 15 | -------- 16 | >>> with imod.util.context.ignore_warnings(): 17 | function_that_throws_warnings() 18 | 19 | """ 20 | with warnings.catch_warnings(): 21 | warnings.simplefilter("ignore", RuntimeWarning) 22 | yield 23 | 24 | 25 | @contextlib.contextmanager 26 | def cd(path: Union[str, pathlib.Path]): 27 | """ 28 | Change directory, and change it back after the with block. 29 | 30 | Examples 31 | -------- 32 | >>> with imod.util.context.cd("docs"): 33 | do_something_in_docs() 34 | 35 | """ 36 | curdir = os.getcwd() 37 | os.chdir(path) 38 | try: 39 | yield 40 | finally: 41 | os.chdir(curdir) 42 | 43 | 44 | @contextlib.contextmanager 45 | def print_if_error(exception_type: type[BaseException]): 46 | """ 47 | Prints error instead of raising it. Useful for cases when pieces of code are 48 | expected to fail, but the script needs to continue. This is a common 49 | use-case for examples in the documentation. 50 | 51 | Parameters 52 | ---------- 53 | exception_type: Exception 54 | Exception to accept 55 | 56 | Examples 57 | -------- 58 | >>> with print_if_error(TypeError): 59 | 1 + "a" 60 | """ 61 | try: 62 | yield 63 | except exception_type as e: 64 | print(e) 65 | -------------------------------------------------------------------------------- /imod/util/dims.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | from typing import Callable, ParamSpec, TypeVar 3 | 4 | import pandas as pd 5 | 6 | from imod.typing import DropVarsType, GridDataArray, Imod5DataDict, SelSettingsType 7 | from imod.typing.grid import enforce_dim_order 8 | 9 | _DROP_LAYER_KWARGS: SelSettingsType = { 10 | "layer": 0, 11 | "drop": True, 12 | "missing_dims": "ignore", 13 | } 14 | 15 | _DROP_VARS_KWARGS: DropVarsType = { 16 | "names": "layer", 17 | "errors": "ignore", 18 | } 19 | 20 | T = TypeVar("T") 21 | P = ParamSpec("P") 22 | 23 | 24 | def enforced_dim_order(func: Callable[P, T]) -> Callable[P, T]: 25 | """Decorator to enforce dimension order after function call""" 26 | 27 | @wraps(func) 28 | def decorator(*args: P.args, **kwargs: P.kwargs): 29 | x = func(*args, **kwargs) 30 | # Multiple grids returned 31 | if isinstance(x, tuple): 32 | return tuple(enforce_dim_order(i) for i in x) 33 | return enforce_dim_order(x) 34 | 35 | return decorator 36 | 37 | 38 | def _drop_layer_dim_and_coord( 39 | da: GridDataArray, 40 | ) -> GridDataArray: 41 | """ 42 | Drop the layer dimension and the layer coord if present from the given 43 | DataArray. 44 | """ 45 | return da.isel(**_DROP_LAYER_KWARGS).compute().drop_vars(**_DROP_VARS_KWARGS) 46 | 47 | 48 | def _drop_layer_if_dataarray( 49 | da: GridDataArray | pd.DataFrame, 50 | ) -> GridDataArray | pd.DataFrame: 51 | # There can be a dataframe in the cap data in case sprinkling is used with 52 | # IPF. 53 | if isinstance(da, pd.DataFrame): 54 | return da 55 | return _drop_layer_dim_and_coord(da) 56 | 57 | 58 | def drop_layer_dim_cap_data(imod5_data: Imod5DataDict) -> Imod5DataDict: 59 | cap_data = imod5_data["cap"] 60 | return {"cap": {key: _drop_layer_if_dataarray(da) for key, da in cap_data.items()}} 61 | -------------------------------------------------------------------------------- /imod/util/imports.py: -------------------------------------------------------------------------------- 1 | class MissingOptionalModule: 2 | """ 3 | Presents a clear error for optional modules. 4 | """ 5 | 6 | def __init__(self, name): 7 | self.name = name 8 | 9 | def __getattr__(self, name): 10 | raise ImportError(f"{self.name} is required for this functionality") 11 | -------------------------------------------------------------------------------- /imod/visualize/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module to provide functions to create customized plots. 3 | 4 | These functions build on existing libraries, and merely serve as a shorthand for 5 | plots that may be useful to evaluate groundwater models. All 6 | ``xarray.DataArray`` and ``pandas.DataFrame`` objects have ``.plot()`` or 7 | ``.imshow()`` methods to plot them directly. 8 | """ 9 | 10 | from imod.visualize.cross_sections import cross_section, quiver, streamfunction 11 | from imod.visualize.pyvista import ( 12 | GridAnimation3D, 13 | StaticGridAnimation3D, 14 | grid_3d, 15 | line_3d, 16 | ) 17 | from imod.visualize.spatial import imshow_topview, plot_map, read_imod_legend 18 | from imod.visualize.waterbalance import waterbalance_barchart 19 | -------------------------------------------------------------------------------- /imod/wq/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Create Water Quality model. 3 | 4 | Create an :class:'imod.wq.SeawatModel' and add desired packages to the model 5 | (e.g. :class:'imod.wq.Well', :class:'imod.wq.Dispersion'). See :doc:'/examples' 6 | and :doc:'/model' for workflow documentation. 7 | """ 8 | 9 | # make classes directly available under imod.wq 10 | from imod.wq.adv import ( 11 | AdvectionFiniteDifference, 12 | AdvectionHybridMOC, 13 | AdvectionMOC, 14 | AdvectionModifiedMOC, 15 | AdvectionTVD, 16 | ) 17 | from imod.wq.bas import BasicFlow 18 | from imod.wq.btn import BasicTransport 19 | from imod.wq.chd import ConstantHead 20 | from imod.wq.dis import TimeDiscretization 21 | from imod.wq.drn import Drainage 22 | from imod.wq.dsp import Dispersion 23 | from imod.wq.evt import ( 24 | EvapotranspirationHighestActive, 25 | EvapotranspirationLayers, 26 | EvapotranspirationTopLayer, 27 | ) 28 | from imod.wq.ghb import GeneralHeadBoundary 29 | from imod.wq.lpf import LayerPropertyFlow 30 | from imod.wq.mal import MassLoading 31 | from imod.wq.model import SeawatModel 32 | from imod.wq.oc import OutputControl 33 | from imod.wq.rch import RechargeHighestActive, RechargeLayers, RechargeTopLayer 34 | from imod.wq.riv import River 35 | from imod.wq.slv import ( 36 | GeneralizedConjugateGradientSolver, 37 | ParallelKrylovFlowSolver, 38 | ParallelKrylovTransportSolver, 39 | PreconditionedConjugateGradientSolver, 40 | ) 41 | from imod.wq.tvc import TimeVaryingConstantConcentration 42 | from imod.wq.vdf import VariableDensityFlow 43 | from imod.wq.wel import Well 44 | -------------------------------------------------------------------------------- /imod/wq/chd.py: -------------------------------------------------------------------------------- 1 | from imod.wq.pkgbase import BoundaryCondition 2 | 3 | 4 | class ConstantHead(BoundaryCondition): 5 | """ 6 | The Constant Head package. The Time-Variant Specified-Head package is used 7 | to simulate specified head boundaries that can change within or between 8 | stress periods. 9 | 10 | Parameters 11 | ---------- 12 | head_start: xr.DataArray of floats 13 | is the head at the boundary at the start of the stress period. 14 | head_end: xr.DataArray of floats 15 | is the head at the boundary at the end of the stress period. 16 | concentration: xr.DataArray of floats 17 | concentrations for the constant heads. It gets automatically written to 18 | the SSM package. 19 | save_budget: bool, optional 20 | is a flag indicating if the budget should be saved (ICHDCB). 21 | Default is False. 22 | """ 23 | 24 | _pkg_id = "chd" 25 | _mapping = (("shead", "head_start"), ("ehead", "head_end")) 26 | 27 | def __init__(self, head_start, head_end, concentration, save_budget=False): 28 | super().__init__() 29 | self["head_start"] = head_start 30 | self["head_end"] = head_end 31 | self["concentration"] = concentration 32 | self["save_budget"] = save_budget 33 | 34 | def _pkgcheck(self, ibound=None): 35 | self._check_positive(["concentration"]) 36 | self._check_location_consistent(["head_start", "head_end", "concentration"]) 37 | 38 | def repeat_stress(self, head_start=None, head_end=None, use_cftime=False): 39 | varnames = ["head_start", "head_end"] 40 | values = [head_start, head_end] 41 | for varname, value in zip(varnames, values): 42 | self._repeat_stress(varname, value, use_cftime) 43 | -------------------------------------------------------------------------------- /imod/wq/drn.py: -------------------------------------------------------------------------------- 1 | from imod.wq.pkgbase import BoundaryCondition 2 | 3 | 4 | class Drainage(BoundaryCondition): 5 | """ 6 | The Drain package is used to simulate head-dependent flux boundaries. In the 7 | Drain package if the head in the cell falls below a certain threshold, the 8 | flux from the drain to the model cell drops to zero. 9 | 10 | Parameters 11 | ---------- 12 | elevation: float or xr.DataArray of floats 13 | elevation of the drain. 14 | conductance: float or xr.DataArray of floats 15 | is the conductance of the drain. 16 | save_budget: bool, optional 17 | A flag that is used to determine if cell-by-cell budget data should be 18 | saved. If save_budget is True cell-by-cell budget data will be saved. 19 | Default is False. 20 | """ 21 | 22 | _pkg_id = "drn" 23 | 24 | _mapping = (("elevation", "elevation"), ("cond", "conductance")) 25 | 26 | def __init__(self, elevation, conductance, save_budget=False): 27 | super().__init__() 28 | self["elevation"] = elevation 29 | self["conductance"] = conductance 30 | self["save_budget"] = save_budget 31 | 32 | def _pkgcheck(self, ibound=None): 33 | self._check_positive(["conductance"]) 34 | self._check_location_consistent(["elevation", "conductance"]) 35 | 36 | def repeat_stress(self, elevation=None, conductance=None, use_cftime=False): 37 | varnames = ["elevation", "conductance"] 38 | values = [elevation, conductance] 39 | for varname, value in zip(varnames, values): 40 | self._repeat_stress(varname, value, use_cftime) 41 | -------------------------------------------------------------------------------- /imod/wq/mal.py: -------------------------------------------------------------------------------- 1 | from imod.wq.pkgbase import BoundaryCondition 2 | 3 | 4 | class MassLoading(BoundaryCondition): 5 | """ 6 | Mass loading package. Has no direct effect on groundwater flow, is only 7 | included via MT3DMS source and sinks. (SSM ITYPE 15) 8 | 9 | Parameters 10 | ---------- 11 | concentration: xr.DataArray of floats 12 | """ 13 | 14 | _pkg_id = "mal" 15 | 16 | def __init__(self, concentration): 17 | super().__init__() 18 | self["concentration"] = concentration 19 | 20 | def repeat_stress(self, concentration, use_cftime=False): 21 | self._repeat_stress("concentration", concentration, use_cftime) 22 | 23 | def _pkgcheck(self, ibound=None): 24 | self._check_positive(["concentration"]) 25 | -------------------------------------------------------------------------------- /imod/wq/tvc.py: -------------------------------------------------------------------------------- 1 | from imod.wq.pkgbase import BoundaryCondition 2 | 3 | 4 | class TimeVaryingConstantConcentration(BoundaryCondition): 5 | """ 6 | Time varying constant concentration package. Has no direct effect on 7 | groundwater flow, is only included via MT3DMS source and sinks. (SSM ITYPE 8 | -1) 9 | 10 | Parameters 11 | ---------- 12 | concentration: xr.DataArray of floats 13 | """ 14 | 15 | _pkg_id = "tvc" 16 | 17 | def __init__(self, concentration): 18 | super().__init__() 19 | self["concentration"] = concentration 20 | 21 | def repeat_stress(self, concentration, use_cftime=False): 22 | self._repeat_stress("concentration", concentration, use_cftime) 23 | 24 | def _pkgcheck(self, ibound=None): 25 | self._check_positive(["concentration"]) 26 | --------------------------------------------------------------------------------