├── .flake8 ├── .gitattributes ├── .github ├── actions │ └── prepare-poetry │ │ └── action.yml ├── codecov.yml └── workflows │ ├── ci-publish.yml │ ├── ci-tests.yml │ └── codeql-analysis.yml ├── .gitignore ├── .readthedocs.yaml ├── LICENSE ├── README.md ├── docs ├── CONTRIBUTING.rst ├── _static │ └── theme_overrides.css ├── _templates │ ├── custom-class-template.rst │ └── custom-module-template.rst ├── conf.py ├── getting-started.rst ├── index.rst ├── installation.rst ├── release-notes.rst ├── requirements-extras.txt ├── scripts │ └── schema.py ├── trademarks.rst └── tutorial │ ├── attributes_tutorial.rst │ ├── cellular_grids.rst │ ├── creating_test.rst │ ├── getting_started_with_model.rst │ ├── grid_properties.rst │ ├── high_level_objects.rst │ ├── images │ ├── S-bend_1.png │ ├── S-bend_2.png │ └── relperm_pyscal_plots.png │ ├── intro_to_resqml.rst │ ├── multiprocessing.rst │ ├── single_property.rst │ ├── surfaces.rst │ ├── tutorial_index.rst │ ├── units_of_measure.rst │ ├── well_basics.rst │ ├── working_with_coord.rst │ └── working_with_equinor_pyscal.rst ├── example_data ├── block.epc ├── block.h5 ├── s_bend.epc ├── s_bend.h5 ├── tic_tac_toe.epc └── tic_tac_toe.h5 ├── poetry.lock ├── pyproject.toml ├── resqpy ├── __init__.py ├── crs.py ├── derived_model │ ├── __init__.py │ ├── _add_edges_per_column_property_array.py │ ├── _add_faults.py │ ├── _add_one_blocked_well_property.py │ ├── _add_one_grid_property_array.py │ ├── _add_single_cell_grid.py │ ├── _add_wells_from_ascii_file.py │ ├── _add_zone_by_layer_property.py │ ├── _coarsened_grid.py │ ├── _common.py │ ├── _copy_grid.py │ ├── _drape_to_surface.py │ ├── _extract_box.py │ ├── _extract_box_for_well.py │ ├── _fault_throw_scaling.py │ ├── _gather_ensemble.py │ ├── _interpolated_grid.py │ ├── _local_depth_adjustment.py │ ├── _refined_grid.py │ ├── _tilted_grid.py │ ├── _unsplit_grid.py │ ├── _zonal_grid.py │ └── _zone_layer_ranges_from_array.py ├── fault │ ├── __init__.py │ ├── _gcs_functions.py │ └── _grid_connection_set.py ├── grid │ ├── __init__.py │ ├── _cell_properties.py │ ├── _connection_sets.py │ ├── _create_grid_xml.py │ ├── _defined_geometry.py │ ├── _extract_functions.py │ ├── _face_functions.py │ ├── _faults.py │ ├── _grid.py │ ├── _grid_types.py │ ├── _intervals_info.py │ ├── _pillars.py │ ├── _pixel_maps.py │ ├── _points_functions.py │ ├── _regular_grid.py │ ├── _transmissibility.py │ ├── _write_hdf5_from_caches.py │ ├── _write_nexus_corp.py │ └── _xyz.py ├── grid_surface │ ├── __init__.py │ ├── _blocked_well_populate.py │ ├── _find_faces.py │ ├── _grid_skin.py │ ├── _grid_surface.py │ ├── _trajectory_intersects.py │ └── grid_surface_cuda.py ├── lines │ ├── __init__.py │ ├── _common.py │ ├── _polyline.py │ └── _polyline_set.py ├── model │ ├── __init__.py │ ├── _catalogue.py │ ├── _context.py │ ├── _forestry.py │ ├── _grids.py │ ├── _hdf5.py │ ├── _model.py │ └── _xml.py ├── multi_processing │ ├── __init__.py │ ├── _multiprocessing.py │ └── wrappers │ │ ├── __init__.py │ │ ├── blocked_well_mp.py │ │ ├── grid_surface_mp.py │ │ └── mesh_mp.py ├── olio │ ├── __init__.py │ ├── ab_toolbox.py │ ├── base.py │ ├── box_utilities.py │ ├── class_dict.py │ ├── consolidation.py │ ├── data │ │ ├── build.py │ │ └── properties.json │ ├── dataframe.py │ ├── exceptions.py │ ├── factors.py │ ├── fine_coarse.py │ ├── grid_functions.py │ ├── intersection.py │ ├── keyword_files.py │ ├── load_data.py │ ├── point_inclusion.py │ ├── random_seed.py │ ├── read_nexus_fault.py │ ├── relperm.py │ ├── simple_lines.py │ ├── time.py │ ├── trademark.py │ ├── transmission.py │ ├── triangulation.py │ ├── uuid.py │ ├── vdb.py │ ├── vector_utilities.py │ ├── volume.py │ ├── wellspec_keywords.py │ ├── write_data.py │ ├── write_hdf5.py │ ├── xml_et.py │ ├── xml_namespaces.py │ └── zmap_reader.py ├── organize │ ├── __init__.py │ ├── _utils.py │ ├── boundary_feature.py │ ├── boundary_feature_interpretation.py │ ├── earth_model_interpretation.py │ ├── fault_interpretation.py │ ├── fluid_boundary_feature.py │ ├── frontier_feature.py │ ├── generic_interpretation.py │ ├── genetic_boundary_feature.py │ ├── geobody_boundary_interpretation.py │ ├── geobody_feature.py │ ├── geobody_interpretation.py │ ├── geologic_unit_feature.py │ ├── horizon_interpretation.py │ ├── organization_feature.py │ ├── rock_fluid_unit_feature.py │ ├── structural_organization_interpretation.py │ ├── tectonic_boundary_feature.py │ ├── wellbore_feature.py │ └── wellbore_interpretation.py ├── property │ ├── __init__.py │ ├── _collection_add_part.py │ ├── _collection_create_xml.py │ ├── _collection_get_attributes.py │ ├── _collection_support.py │ ├── _property.py │ ├── attribute_property_set.py │ ├── grid_property_collection.py │ ├── property_collection.py │ ├── property_common.py │ ├── property_kind.py │ ├── string_lookup.py │ ├── well_interval_property.py │ ├── well_interval_property_collection.py │ ├── well_log.py │ └── well_log_collection.py ├── rq_import │ ├── __init__.py │ ├── _add_ab_properties.py │ ├── _add_surfaces.py │ ├── _grid_from_cp.py │ ├── _import_nexus.py │ ├── _import_vdb_all_grids.py │ └── _import_vdb_ensemble.py ├── strata │ ├── __init__.py │ ├── _binary_contact_interpretation.py │ ├── _geologic_unit_interpretation.py │ ├── _strata_common.py │ ├── _stratigraphic_column.py │ ├── _stratigraphic_column_rank.py │ ├── _stratigraphic_unit_feature.py │ └── _stratigraphic_unit_interpretation.py ├── surface │ ├── __init__.py │ ├── _base_surface.py │ ├── _combined_surface.py │ ├── _mesh.py │ ├── _pointset.py │ ├── _surface.py │ ├── _tri_mesh.py │ ├── _tri_mesh_stencil.py │ └── _triangulated_patch.py ├── time_series │ ├── __init__.py │ ├── _any_time_series.py │ ├── _from_nexus_summary.py │ ├── _functions.py │ ├── _geologic_time_series.py │ ├── _time_duration.py │ └── _time_series.py ├── unstructured │ ├── __init__.py │ ├── _hexa_grid.py │ ├── _prism_grid.py │ ├── _pyramid_grid.py │ ├── _tetra_grid.py │ └── _unstructured_grid.py ├── weights_and_measures │ ├── __init__.py │ ├── nexus_units.py │ └── weights_and_measures.py └── well │ ├── __init__.py │ ├── _blocked_well.py │ ├── _deviation_survey.py │ ├── _md_datum.py │ ├── _trajectory.py │ ├── _wellbore_frame.py │ ├── _wellbore_marker.py │ ├── _wellbore_marker_frame.py │ ├── blocked_well_frame.py │ ├── well_object_funcs.py │ └── well_utils.py └── tests ├── __init__.py ├── conftest.py ├── integration_tests ├── conftest.py ├── snapshots │ └── transmissibility │ │ ├── block_i.txt │ │ ├── block_j.txt │ │ └── block_k.txt └── test_grid_transmissibility_snapshots.py ├── test_data ├── Charisma_example.txt ├── Charisma_points.txt ├── IRAP_example.txt ├── IRAP_points.txt ├── README.md ├── Surface_roxartext.txt ├── Surface_roxartext_rotate.txt ├── Surface_tsurf.txt ├── Surface_tsurf_2.txt ├── Surface_zmap.dat ├── dateless_timeseries.sum ├── facies.ib ├── fault_1.inc ├── fault_2.inc ├── fault_3.inc ├── logs.las ├── ntg_355.db └── wren │ ├── ACTIVE │ ├── INJECT_wellspec │ ├── NETGRS_r_0 │ ├── NETGRS_r_1 │ ├── NETGRS_r_2 │ ├── PORO_r_0 │ ├── PORO_r_1 │ ├── PORO_r_2 │ ├── PRODUCE_wellspec │ ├── perm_direction_IJ_r_0 │ ├── perm_direction_IJ_r_1 │ ├── perm_direction_IJ_r_2 │ ├── perm_direction_K_r_0 │ ├── perm_direction_K_r_1 │ ├── perm_direction_K_r_2 │ ├── wren.corp │ ├── wren.epc │ ├── wren.h5 │ ├── wren0.sum │ ├── wren0.vdb │ ├── main.xml │ └── wren0 │ │ ├── ALIAS │ │ └── alias.bin │ │ ├── INIT │ │ ├── MAPDATA │ │ │ ├── IROOTBV.bin │ │ │ ├── IROOTDAD.bin │ │ │ ├── IROOTDEADCELL.bin │ │ │ ├── IROOTDXC.bin │ │ │ ├── IROOTDYC.bin │ │ │ ├── IROOTDZC.bin │ │ │ ├── IROOTDZN.bin │ │ │ ├── IROOTKH.bin │ │ │ ├── IROOTKID.bin │ │ │ ├── IROOTMDEP.bin │ │ │ ├── IROOTPVR.bin │ │ │ ├── IROOTUID.bin │ │ │ └── IROOTUNPACK.bin │ │ ├── ROOT_corp.bin │ │ ├── ROOT_faultInfo.bin │ │ └── nsc_conn.fbin │ │ ├── RECUR │ │ ├── MAPDATA │ │ │ ├── RROOT_0.bin │ │ │ ├── RROOT_22.bin │ │ │ ├── RROOT_34.bin │ │ │ └── RROOT_4.bin │ │ └── perfts.bin │ │ ├── ThresholdSizeCache.xml │ │ ├── WELIST │ │ └── welist.bin │ │ └── case.ctrl │ ├── wren1.sum │ ├── wren1.vdb │ ├── main.xml │ └── wren1 │ │ ├── ALIAS │ │ └── alias.bin │ │ ├── INIT │ │ ├── MAPDATA │ │ │ ├── IROOTBV.bin │ │ │ ├── IROOTDAD.bin │ │ │ ├── IROOTDEADCELL.bin │ │ │ ├── IROOTDXC.bin │ │ │ ├── IROOTDYC.bin │ │ │ ├── IROOTDZC.bin │ │ │ ├── IROOTDZN.bin │ │ │ ├── IROOTKH.bin │ │ │ ├── IROOTKID.bin │ │ │ ├── IROOTMDEP.bin │ │ │ ├── IROOTPVR.bin │ │ │ ├── IROOTUID.bin │ │ │ └── IROOTUNPACK.bin │ │ ├── ROOT_corp.bin │ │ ├── ROOT_faultInfo.bin │ │ └── nsc_conn.fbin │ │ ├── RECUR │ │ ├── MAPDATA │ │ │ ├── RROOT_0.bin │ │ │ ├── RROOT_22.bin │ │ │ ├── RROOT_34.bin │ │ │ └── RROOT_4.bin │ │ └── perfts.bin │ │ ├── ThresholdSizeCache.xml │ │ ├── WELIST │ │ └── welist.bin │ │ └── case.ctrl │ ├── wren2.sum │ └── wren2.vdb │ ├── main.xml │ └── wren2 │ ├── ALIAS │ └── alias.bin │ ├── INIT │ ├── MAPDATA │ │ ├── IROOTBV.bin │ │ ├── IROOTDAD.bin │ │ ├── IROOTDEADCELL.bin │ │ ├── IROOTDXC.bin │ │ ├── IROOTDYC.bin │ │ ├── IROOTDZC.bin │ │ ├── IROOTDZN.bin │ │ ├── IROOTKH.bin │ │ ├── IROOTKID.bin │ │ ├── IROOTMDEP.bin │ │ ├── IROOTPVR.bin │ │ ├── IROOTUID.bin │ │ └── IROOTUNPACK.bin │ ├── ROOT_corp.bin │ ├── ROOT_faultInfo.bin │ └── nsc_conn.fbin │ ├── RECUR │ ├── MAPDATA │ │ ├── RROOT_0.bin │ │ ├── RROOT_22.bin │ │ ├── RROOT_34.bin │ │ └── RROOT_4.bin │ └── perfts.bin │ ├── ThresholdSizeCache.xml │ ├── WELIST │ └── welist.bin │ └── case.ctrl └── unit_tests ├── conftest.py ├── derived_model └── test_derived_model.py ├── fault ├── test_fault.py ├── test_fault_connection_set.py └── test_fault_interpretation.py ├── grid ├── conftest.py ├── test_cell_properties.py ├── test_defined_geometry.py ├── test_face_functions.py ├── test_faults.py ├── test_grid.py ├── test_parametric_line_geometry.py ├── test_points_functions.py ├── test_regular_grid.py ├── test_write_nexus_corp.py └── test_xyz.py ├── lines ├── test_lines.py └── test_polyline_set.py ├── model └── test_model.py ├── multiprocessing ├── test_function_multiprocessing.py ├── test_grid_surface_mp.py └── test_mesh_mp.py ├── olio ├── conftest.py ├── test_base.py ├── test_box_utilities.py ├── test_dataframe.py ├── test_factors.py ├── test_fine_coarse.py ├── test_intersection.py ├── test_olio_grid_functions.py ├── test_olio_write_data.py ├── test_point_inclusion.py ├── test_read_nexus_fault.py ├── test_relperm.py ├── test_simple_lines.py ├── test_transmission.py ├── test_triangulation.py ├── test_uuid.py ├── test_vdb.py ├── test_vector_utilities.py ├── test_volume.py ├── test_wellspec_keywords.py ├── test_write_hdf5.py └── test_xml_et.py ├── organize └── test_organize.py ├── property ├── test_attribute_property_set.py ├── test_property.py └── test_unstructured_grid_properties.py ├── rq_import ├── test_import_vdb_ensemble.py └── test_rq_import.py ├── strata └── test_strata.py ├── surface ├── test_surface.py ├── test_surface_patches.py ├── test_surface_pointset.py ├── test_tri_mesh.py └── test_tri_mesh_stencil.py ├── test_crs.py ├── test_grid_surface.py ├── test_resqpy.py ├── test_weights_measures.py ├── time_series ├── test_any_time_series.py ├── test_time_duration.py ├── test_time_series.py ├── test_time_series_from_nexus.py └── test_time_series_functions.py ├── unstructured └── test_unstructured.py └── well ├── test_blocked_well.py ├── test_deviation_survey.py ├── test_md_datum.py ├── test_s_bend.py ├── test_trajectory.py ├── test_well.py ├── test_well_object_funcs.py ├── test_well_utils.py ├── test_wellbore_frame.py ├── test_wellbore_marker.py └── test_wellbore_marker_frame.py /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | # count = True 3 | # statistics = True 4 | show_source = True 5 | docstring-convention = google 6 | exclude = .git, __pycache__, .ipynb_checkpoints, venv, dist, .venv 7 | # error code reference: https://gist.github.com/sharkykh/c76c80feadc8f33b129d846999210ba3 8 | select = 9 | # Logical errors 10 | F, 11 | # Indentation 12 | E101, E112, E113, W19, 13 | # Statements 14 | E71, E72, E73, E74, 15 | # Runtime 16 | E9, 17 | # Deprecation 18 | W60, 19 | # Docstrings (pydocstyle) 20 | D 21 | ignore = 22 | # F401: "module imported but unused". Ignoring as low-priority 23 | F401, 24 | # F841: "local variable is assigned to but never used". Ignoring as sometimes deliberate 25 | F841, 26 | # pydocstyle exceptions for Google-style docstrings 27 | # See http://www.pydocstyle.org/en/latest/error_codes.html#default-conventions 28 | D203, D204, D213, D215, D400, D401, D404, D406, D407, D408, D409, D413 29 | # Further pydocstyle exceptions for relatively unimportant issues 30 | D202, D210, D402, D405, D415, D417 31 | per-file-ignores = 32 | # Do not check docstrings for the tests or docs directories 33 | tests/*: D 34 | docs/*: D 35 | resqpy/version.py: D 36 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Ensure consitent treatment of line endings 2 | * text=auto 3 | *.py text 4 | *.bin -diff 5 | -------------------------------------------------------------------------------- /.github/actions/prepare-poetry/action.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Prepare Python and Poetry 4 | Description: Install Python, Poetry and dev dependencies, cached for speed 5 | 6 | inputs: 7 | python-version: 8 | description: 'Python version to use' 9 | required: true 10 | default: '3.x' 11 | 12 | runs: 13 | using: "composite" 14 | steps: 15 | - name: Set up Python 16 | id: setup-python 17 | uses: actions/setup-python@v5 18 | with: 19 | python-version: ${{ inputs.python-version }} 20 | 21 | - name: Load cached Poetry installation 22 | uses: actions/cache@v4 23 | with: 24 | path: ~/.local # the path depends on the OS 25 | key: poetry-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-0 # increment to reset cache 26 | 27 | - name: Install Poetry 28 | uses: snok/install-poetry@v1 29 | with: 30 | version: 1.2.2 31 | virtualenvs-create: true 32 | virtualenvs-in-project: true 33 | 34 | - name: Install Poetry Plugins 35 | run: | 36 | python -m pip install --upgrade pip 37 | pip install poetry-dynamic-versioning 38 | shell: bash 39 | 40 | - name: Load cached venv 41 | id: cached-poetry-dependencies 42 | uses: actions/cache@v4 43 | with: 44 | path: .venv 45 | key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}-1 46 | 47 | - name: Install dependencies and library 48 | # if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' 49 | run: poetry install --no-interaction 50 | shell: bash 51 | -------------------------------------------------------------------------------- /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Configuration for CodeCov 3 | # See https://docs.codecov.io/docs/codecov-yaml 4 | 5 | codecov: 6 | require_ci_to_pass: yes 7 | 8 | coverage: 9 | # Colour chart range 10 | # In future, aim to update range to stricter standard 11 | range: "20...80" 12 | 13 | comment: 14 | # Only post a comment if coverage changes 15 | require_changes: true 16 | -------------------------------------------------------------------------------- /.github/workflows/ci-publish.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Publish release 4 | 5 | on: 6 | push: 7 | tags: 8 | # Run when a tag is pushed with a valid semantic version number 9 | - 'v[0-9]+.[0-9]+.[0-9]+' 10 | 11 | jobs: 12 | build: 13 | name: Build distribution 14 | runs-on: ubuntu-latest 15 | steps: 16 | 17 | - name: Checkout code 18 | uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | 22 | - name: Set up Python 23 | uses: actions/setup-python@v5 24 | with: 25 | python-version: '3.10' 26 | 27 | - name: Install Poetry 28 | run: | 29 | python -m pip install --upgrade pip 30 | pip install poetry 31 | pip install poetry-dynamic-versioning 32 | 33 | - name: Build 34 | run: | 35 | poetry build 36 | 37 | - name: Save build artifacts 38 | uses: actions/upload-artifact@v4 39 | with: 40 | name: dist-artifact 41 | path: dist/ 42 | 43 | test: 44 | name: Test 45 | needs: build 46 | runs-on: ubuntu-latest 47 | steps: 48 | 49 | - name: Set up Python 50 | uses: actions/setup-python@v5 51 | with: 52 | python-version: '3.10' 53 | 54 | - name: Checkout code 55 | uses: actions/checkout@v4 56 | with: 57 | fetch-depth: 0 58 | 59 | - name: Get build artifacts 60 | uses: actions/download-artifact@v4 61 | with: 62 | name: dist-artifact 63 | path: dist/ 64 | 65 | - name: Install from build 66 | run: | 67 | python -m pip install --upgrade pip 68 | pip install pytest-mock 69 | pip install resqpy --pre --target=dist --find-links=dist/ 70 | 71 | - name: Copy tests and example data to dist folder for testing against artifacts 72 | run: | 73 | cp -R tests dist/ 74 | cp -R example_data dist/ 75 | 76 | - name: Run tests 77 | run: pytest dist/tests --buildtest 78 | 79 | publish: 80 | name: Publish to PyPI 81 | needs: [build, test] 82 | runs-on: ubuntu-latest 83 | environment: 84 | name: pypi 85 | url: https://pypi.org/p/resqpy 86 | permissions: 87 | id-token: write 88 | steps: 89 | 90 | - name: Get build artifacts 91 | uses: actions/download-artifact@v4 92 | with: 93 | name: dist-artifact 94 | path: dist/ 95 | 96 | - name: Publish to PyPI 97 | uses: pypa/gh-action-pypi-publish@release/v1 98 | -------------------------------------------------------------------------------- /.github/workflows/ci-tests.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Tests 4 | 5 | on: 6 | push: 7 | branches: 8 | - master 9 | pull_request: 10 | 11 | jobs: 12 | static-analysis: 13 | name: Static analysis 14 | runs-on: ubuntu-latest 15 | steps: 16 | 17 | - name: Checkout code 18 | uses: actions/checkout@v4 19 | 20 | - name: Install dependencies 21 | uses: ./.github/actions/prepare-poetry 22 | with: 23 | python-version: "3.10" 24 | 25 | # Post in-line comments for any issues found 26 | # Do not run if coming from a forked repo 27 | # See https://github.com/marketplace/actions/lint-action 28 | - name: Run linters (with annotations) 29 | if: github.event.pull_request.head.repo.full_name == github.repository 30 | uses: wearerequired/lint-action@v2 31 | with: 32 | flake8: true 33 | flake8_command_prefix: poetry run 34 | flake8_dir: resqpy 35 | mypy: true 36 | mypy_command_prefix: poetry run 37 | mypy_args: resqpy 38 | 39 | # Alternative step that works with forked repo 40 | - name: Run linters (without annotations) 41 | if: github.event.pull_request.head.repo.full_name != github.repository 42 | run: | 43 | poetry run flake8 . 44 | poetry run mypy resqpy 45 | 46 | - name: Code formatting 47 | run: poetry run yapf --diff -r -p . 48 | 49 | unit-tests: 50 | name: Unit tests (Python ${{ matrix.python-version }}) 51 | runs-on: ubuntu-latest 52 | 53 | strategy: 54 | matrix: 55 | python-version: ["3.9", "3.10", "3.11", "3.12"] 56 | 57 | steps: 58 | - uses: actions/checkout@v4 59 | 60 | - name: Install dependencies 61 | uses: ./.github/actions/prepare-poetry 62 | with: 63 | python-version: ${{ matrix.python-version }} 64 | 65 | - name: Run pytest 66 | run: poetry run pytest --cov=resqpy --cov-report=xml --junitxml=pytest.xml 67 | 68 | - name: Upload pytest artifacts 69 | if: ${{ always() }} 70 | uses: actions/upload-artifact@v4 71 | with: 72 | name: Unit Test Results (Python ${{ matrix.python-version }}) 73 | path: pytest.xml 74 | 75 | - name: Upload coverage to Codecov 76 | uses: codecov/codecov-action@v5 77 | with: 78 | token: ${{ secrets.CODECOV_TOKEN }} 79 | files: coverage.xml 80 | fail_ci_if_error: true 81 | 82 | legacy-dependencies: 83 | # Test against older versions of numpy and pandas, 84 | # To ensure resqpy works with the lower bounds of our published constraints 85 | 86 | name: Unit tests (older dependencies) 87 | runs-on: ubuntu-latest 88 | 89 | steps: 90 | - uses: actions/checkout@v4 91 | 92 | - name: Install dependencies 93 | uses: ./.github/actions/prepare-poetry 94 | with: 95 | python-version: 3.9 96 | 97 | - name: Downgrade numpy and pandas 98 | # Use caret-style requirements (~) to fix minor version but allow patches 99 | run: | 100 | poetry add numpy@~1.26 pandas@~2.2 101 | poetry show 102 | 103 | - name: Run pytest 104 | run: poetry run pytest 105 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: CodeQL 4 | 5 | on: 6 | push: 7 | branches: [ master ] 8 | # pull_request: 9 | # The branches below must be a subset of the branches above 10 | # branches: [ master ] 11 | schedule: 12 | # 10:16 on Fridays 13 | - cron: '16 10 * * 5' 14 | 15 | jobs: 16 | analyze: 17 | name: Analyze 18 | runs-on: ubuntu-latest 19 | permissions: 20 | actions: read 21 | contents: read 22 | security-events: write 23 | 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | language: [ 'python' ] 28 | 29 | steps: 30 | - name: Checkout repository 31 | uses: actions/checkout@v4 32 | 33 | # Initializes the CodeQL tools for scanning. 34 | - name: Initialize CodeQL 35 | uses: github/codeql-action/init@v2 36 | with: 37 | languages: ${{ matrix.language }} 38 | # If you wish to specify custom queries, you can do so here or in a config file. 39 | # By default, queries listed here will override any specified in a config file. 40 | # Prefix the list here with "+" to use these queries and those in the config file. 41 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 42 | 43 | - name: Perform CodeQL Analysis 44 | uses: github/codeql-action/analyze@v2 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE settings 2 | .idea 3 | .vscode 4 | 5 | # Checkpoints 6 | .ipynb_checkpoints 7 | __pycache__/ 8 | .pyc 9 | .DS_Store 10 | 11 | # Unit tests 12 | pytest.xml 13 | .pytest_cache 14 | .coverage 15 | 16 | # Built Documentation 17 | docs/_build 18 | docs/_autosummary 19 | docs/html 20 | 21 | # Build artifacts 22 | build 23 | dist 24 | *.egg-info 25 | 26 | # Dynamically-generated version 27 | resqpy/version.py 28 | 29 | # XSD files 30 | resqpy/olio/data/*.xml 31 | resqpy/olio/data/*.xsd 32 | 33 | # Dask 34 | dask-worker-space 35 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # .readthedocs.yaml 4 | # Read the Docs configuration file 5 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 6 | 7 | version: 2 8 | 9 | build: 10 | os: ubuntu-22.04 11 | tools: 12 | python: "3.11" 13 | 14 | sphinx: 15 | configuration: docs/conf.py 16 | 17 | # Optional additional formats 18 | formats: 19 | - pdf 20 | 21 | # Python environment 22 | # Equivalent to: pip install resqpy 23 | python: 24 | install: 25 | - method: pip 26 | path: . 27 | - requirements: docs/requirements-extras.txt 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 BP 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # resqpy: Python API for working with RESQML models 2 | 3 | [![License](https://img.shields.io/pypi/l/resqpy)](https://github.com/bp/resqpy/blob/master/LICENSE) 4 | [![Documentation Status](https://readthedocs.org/projects/resqpy/badge/?version=latest)](https://resqpy.readthedocs.io/en/latest/?badge=latest) 5 | [![Python CI](https://github.com/bp/resqpy/actions/workflows/ci-tests.yml/badge.svg)](https://github.com/bp/resqpy/actions/workflows/ci-tests.yml) 6 | ![Python version](https://img.shields.io/pypi/pyversions/resqpy) 7 | [![PyPI](https://img.shields.io/pypi/v/resqpy)](https://badge.fury.io/py/resqpy) 8 | ![Status](https://img.shields.io/pypi/status/resqpy) 9 | [![codecov](https://codecov.io/gh/bp/resqpy/branch/master/graph/badge.svg)](https://codecov.io/gh/bp/resqpy) 10 | 11 | ## Introduction 12 | 13 | **resqpy** is a pure Python package which provides a programming interface (API) for 14 | reading, writing, and modifying reservoir models in the RESQML format. It gives 15 | you the ability to work with reservoir models programmatically, without having 16 | to know the details of the RESQML standard. 17 | 18 | The package is written and maintained by bp, and is made available under the MIT 19 | license as a contribution to the open source community. 20 | 21 | **resqpy** was created by Andy Beer. For enquires about resqpy, please contact 22 | Emma Nesbit (Emma.Nesbit@uk.bp.com) 23 | 24 | ### Documentation 25 | 26 | See the complete package documentation on 27 | [readthedocs](https://resqpy.readthedocs.io/). 28 | 29 | ### About RESQML 30 | 31 | RESQML™ is an industry initiative to provide open, non-proprietary data exchange 32 | standards for reservoir characterization, earth and reservoir models. It is 33 | governed by the [Energistics 34 | consortium](https://www.energistics.org/portfolio/resqml-data-standards/). 35 | 36 | Resqpy provides specialized classes for a subset of the RESQML high level object 37 | classes, as described in the docs. Furthermore, not all variations of these 38 | object types are supported; for example, radial IJK grids are not yet catered 39 | for, although the RESQML standard does allow for such grids. 40 | 41 | It is envisaged that the code base will be expanded to include other classes of 42 | object and more fully cover the options permitted by the RESQML standard. 43 | 44 | ## Installation 45 | 46 | Resqpy can be installed with pip: 47 | 48 | ```bash 49 | pip install resqpy 50 | ``` 51 | 52 | Alternatively, to install your working copy of the code in "editable" mode: 53 | 54 | ```bash 55 | pip install -e /path/to/repo/ 56 | ``` 57 | 58 | ## Contributing 59 | 60 | Contributions of all forms are welcome and encouraged! Please feel free to open 61 | issues on the GitHub issue tracker, or submit Pull Requests. Instructions with how to set up your own development environment can be found at [Development environment setup](https://github.com/bp/resqpy/blob/master/docs/CONTRIBUTING.rst#development-environment-setup). Please read the 62 | [Contributing Guide](docs/CONTRIBUTING.rst) before submitting patches. 63 | -------------------------------------------------------------------------------- /docs/_static/theme_overrides.css: -------------------------------------------------------------------------------- 1 | /* override table width restrictions */ 2 | /* see https://rackerlabs.github.io/docs-rackspace/tools/rtd-tables.html */ 3 | @media screen and (min-width: 767px) { 4 | 5 | .wy-table-responsive table td { 6 | /* !important prevents the common CSS stylesheets from overriding 7 | this as on RTD they are loaded after this stylesheet */ 8 | white-space: normal !important; 9 | } 10 | 11 | .wy-table-responsive { 12 | overflow: visible !important; 13 | } 14 | } -------------------------------------------------------------------------------- /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 | :special-members: __init__ 10 | 11 | .. autoclasstoc:: -------------------------------------------------------------------------------- /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 classes %} 18 | {% if classes %} 19 | .. rubric:: {{ _('Classes') }} 20 | 21 | .. autosummary:: 22 | :toctree: 23 | :template: custom-class-template.rst 24 | :nosignatures: 25 | {% for item in classes %} 26 | {{ item }} 27 | {%- endfor %} 28 | {% endif %} 29 | {% endblock %} 30 | 31 | {% block functions %} 32 | {% if functions %} 33 | .. rubric:: {{ _('Functions') }} 34 | 35 | .. autosummary:: 36 | :toctree: 37 | :nosignatures: 38 | {% for item in functions %} 39 | {{ item }} 40 | {%- endfor %} 41 | {% endif %} 42 | {% endblock %} 43 | 44 | {% block exceptions %} 45 | {% if exceptions %} 46 | .. rubric:: {{ _('Exceptions') }} 47 | 48 | .. autosummary:: 49 | :toctree: 50 | {% for item in exceptions %} 51 | {{ item }} 52 | {%- endfor %} 53 | {% endif %} 54 | {% endblock %} 55 | 56 | {% block modules %} 57 | {% if modules %} 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/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # See also https://wwoods.github.io/2016/06/09/easy-sphinx-documentation-without-the-boilerplate/#setup 8 | 9 | # Nb. useful stackexchange thread about the :recursive: option and custom templates: 10 | # https://stackoverflow.com/questions/2701998/sphinx-autodoc-is-not-automatic-enough/62613202#62613202 11 | 12 | # -- Path setup -------------------------------------------------------------- 13 | 14 | # Do not do sys.path surgery 15 | # Instead, resqpy package should be pip-installed in the python environment 16 | 17 | # Mock imports for things not available 18 | # autodoc_mock_imports = ["h5py"] 19 | 20 | # -- Project information ----------------------------------------------------- 21 | 22 | project = 'resqpy' 23 | copyright = '2021, BP' 24 | author = 'BP' 25 | 26 | # Version from git tag 27 | try: 28 | from importlib import metadata 29 | release = metadata.version('resqpy') 30 | except Exception: 31 | release = '0.0.0-version-not-available' 32 | 33 | # Take major/minor 34 | version = '.'.join(release.split('.')[:2]) 35 | 36 | # -- General configuration --------------------------------------------------- 37 | 38 | autoclass_content = "class" # Alternatively, "both" to include init method 39 | # autodoc_default_options = { 40 | # 'members': None, 41 | # 'inherited-members': None, 42 | # 'private-members': None, 43 | # 'show-inheritance': None, 44 | # } 45 | autosummary_generate = True # Make _autosummary files and include them 46 | autosummary_generate_overwrite = True 47 | 48 | napoleon_use_rtype = False # More legible 49 | autodoc_member_order = 'bysource' 50 | # napoleon_numpy_docstring = False # Force consistency, leave only Google 51 | 52 | extensions = [ 53 | 'sphinx.ext.autodoc', 54 | 'sphinx.ext.autosummary', 55 | 'sphinx.ext.napoleon', 56 | 'sphinx.ext.viewcode', 57 | 'autoclasstoc', 58 | ] 59 | 60 | templates_path = ['_templates'] 61 | 62 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '**.ipynb_checkpoints'] 63 | 64 | # -- Options custom autoclasstoc sections ------------------------------------ 65 | 66 | # See https://autoclasstoc.readthedocs.io/en/latest/advanced_usage.html 67 | 68 | from autoclasstoc import PublicMethods 69 | 70 | 71 | class CommomMethods(PublicMethods): 72 | key = "common-methods" 73 | title = "Commonly Used Methods:" 74 | 75 | def predicate(self, name, attr, meta): 76 | return super().predicate(name, attr, meta) and 'common' in meta 77 | 78 | 79 | class OtherMethods(PublicMethods): 80 | key = "other-methods" 81 | title = "Methods:" 82 | 83 | def predicate(self, name, attr, meta): 84 | return super().predicate(name, attr, meta) and 'common' not in meta 85 | 86 | 87 | autoclasstoc_sections = [ 88 | "public-attrs", 89 | "common-methods", 90 | "other-methods", 91 | ] 92 | 93 | # -- Options for HTML output ------------------------------------------------- 94 | 95 | html_theme = 'sphinx_rtd_theme' 96 | 97 | # Override table width restrictions 98 | # See https://rackerlabs.github.io/docs-rackspace/tools/rtd-tables.html 99 | html_static_path = ['_static'] 100 | html_context = { 101 | 'css_files': [ 102 | '_static/theme_overrides.css', # override wide tables in RTD theme 103 | ], 104 | } 105 | -------------------------------------------------------------------------------- /docs/getting-started.rst: -------------------------------------------------------------------------------- 1 | Getting started 2 | =============== 3 | 4 | .. code-block:: python 5 | 6 | >>> import resqpy 7 | 8 | A first step is typically to instantiate a :class:`resqpy.model.Model` object from your `.epc` file: 9 | 10 | .. code-block:: python 11 | 12 | >>> from resqpy.model import Model 13 | >>> model = Model(epc_file='my_file.epc') 14 | 15 | 16 | Models can be conveniently opened with the :class:`resqpy.model.ModelContext` context manager, to ensure file handles are closed properly upon exit: 17 | 18 | .. code-block:: python 19 | 20 | >>> from resqpy.model import ModelContext 21 | >>> with ModelContext("my_model.epc") as model: 22 | >>> print(model.uuids()) 23 | 24 | If you don't have any RESQML datasets, you can use the tiny datasets included in the example_data directory of the resqpy repository. 25 | 26 | To list all the parts (high level objects) in the model: 27 | 28 | .. code-block:: python 29 | 30 | for part in model.parts(): 31 | 32 | part_type = model.type_of_part(part) 33 | title = model.citation_title_for_part(part) 34 | uuid = str(model.uuid_for_part(part)) 35 | 36 | print(f'{title:<30s} {part_type:<35s} {uuid}') 37 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to resqpy's documentation 2 | ================================= 3 | 4 | resqpy is a pure Python package which provides a programming interface (API) for 5 | reading, writing, and modifying reservoir models in the RESQML format. It gives 6 | you the ability to work with reservoir models programmatically, without having 7 | to know the details of the RESQML standard. 8 | 9 | resqpy is written and maintained by bp, and is made available under the MIT 10 | license as a contribution to the open-source community. 11 | 12 | The repository is hosted on `GitHub `_. 13 | 14 | .. toctree:: 15 | :maxdepth: 1 16 | :caption: About 17 | 18 | installation 19 | CONTRIBUTING 20 | release-notes 21 | trademarks 22 | 23 | .. toctree:: 24 | :maxdepth: 2 25 | :caption: User guide 26 | 27 | getting-started 28 | tutorial/tutorial_index 29 | 30 | API Reference 31 | ------------- 32 | 33 | .. automodule:: resqpy 34 | 35 | 36 | Indices and tables 37 | ================== 38 | 39 | * :ref:`genindex` 40 | * :ref:`modindex` 41 | * :ref:`search` 42 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | resqpy is written for Python 3. 5 | 6 | It is recommended to use a conda environment for each new project. 7 | 8 | .. code-block:: bash 9 | 10 | $ conda create -n my_env python=3 11 | $ conda activate my_env 12 | 13 | Install using pip: 14 | 15 | .. code-block:: bash 16 | 17 | $ pip install resqpy 18 | 19 | To install a development version on your local machine, use: 20 | 21 | .. code-block:: bash 22 | 23 | $ pip install -e /path/to/working/copy 24 | 25 | To run unit tests (requires pytest): 26 | 27 | .. code-block:: bash 28 | 29 | $ python -m pytest tests/ 30 | 31 | To build the documentation locally (requires sphinx): 32 | 33 | .. code-block:: bash 34 | 35 | $ sphinx-build docs docs/html 36 | -------------------------------------------------------------------------------- /docs/release-notes.rst: -------------------------------------------------------------------------------- 1 | Release notes 2 | ============= 3 | 4 | For details of changes in each release, see the 5 | `releases `_ page on GitHub. 6 | 7 | Resqpy releases follow [semantic versioning](https://semver.org/). 8 | -------------------------------------------------------------------------------- /docs/requirements-extras.txt: -------------------------------------------------------------------------------- 1 | autoclasstoc==1.3.0 2 | sphinx==5.3.0 3 | sphinx_rtd_theme<1.0.0 4 | -------------------------------------------------------------------------------- /docs/trademarks.rst: -------------------------------------------------------------------------------- 1 | Trademarks 2 | ========== 3 | 4 | The following trademarks appear in resqpy source code and/or diagnostic messages. 5 | 6 | * **RESQML** is a trademark of the Energistics Consortium 7 | * **Nexus** is a registered trademark of the Halliburton Company 8 | * **RMS** and **ROXAR** are registered trademarks of Roxar Software Solutions AS, an Emerson company 9 | * **GOCAD** is also a trademark of Emerson 10 | -------------------------------------------------------------------------------- /docs/tutorial/images/S-bend_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/docs/tutorial/images/S-bend_1.png -------------------------------------------------------------------------------- /docs/tutorial/images/S-bend_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/docs/tutorial/images/S-bend_2.png -------------------------------------------------------------------------------- /docs/tutorial/images/relperm_pyscal_plots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/docs/tutorial/images/relperm_pyscal_plots.png -------------------------------------------------------------------------------- /docs/tutorial/single_property.rst: -------------------------------------------------------------------------------- 1 | Working with a Single Property 2 | ============================== 3 | 4 | The previous tutorial looked at working with sets of property arrays, using the resqpy PropertyCollection class. This tutorial explores an alternative when working with a single property array. The resqpy Property class behaves more like the other resqpy high level object classes. In particular, one resqpy Property object corresponds to one RESQML object of class ContinuousProperty, DiscreteProperty or CategoricalProperty. 5 | 6 | Accessing a property array for a given uuid 7 | ------------------------------------------- 8 | Assuming that an existing RESQML dataset has been opened, we can find the uuid of a particular property using the familiar methods from the Model class. For example: 9 | 10 | .. code-block:: python 11 | 12 | blocked_well_uuid = Model.uuid(obj_type = 'BlockedWellboreRepresentation', title = 'INJECTOR_3') 13 | assert blocked_well_uuid is not None 14 | kh_uuid = Model.uuid(obj_type = 'ContinuousProperty', title = 'KH', related_uuid = blocked_well_uuid) 15 | 16 | It is a good idea to include the *related_uuid argument*, as shown above, to ensure that the property is 'for' the required representation object (in this example a blocked well). Having determined the uuid for a property, we can instantiate the corresponding resqpy object in the familiar way: 17 | 18 | .. code-block:: python 19 | 20 | import resqpy.property as rqp 21 | inj_3_kh = rqp.Property(model, uuid = kh_uuid) 22 | 23 | To get at the actual property data as a numpy array, use the *array_ref()* method: 24 | 25 | .. code-block:: python 26 | 27 | inj_3_kh_array = inj_3_kh.array_ref() 28 | 29 | The *array_ref()* method has some optional arguments for coercing the array element data type (dtype), and for optionally returning a masked array. 30 | 31 | The Property class includes similar methods to PropertyCollection for retrieving the metadata for the property. As the Property object is for a single property, the '_for_part' is dropped from the method names, as is the part argument. For example: 32 | 33 | .. code-block:: python 34 | 35 | assert inj_3_kh.property_kind() == 'permeability thickness' 36 | assert inj_3_kh.indexable_element() == 'cells' 37 | kh_uom = inj_3_kh.uom() 38 | 39 | Behind the scenes, the Property object has a singleton PropertyCollection class as an attribute. This can be used by calling code if access to other PropertyCollection functionality is needed. Here is an example that sets up a normalised version of the array data: 40 | 41 | .. code-block:: python 42 | 43 | normalized_inj_3_kh = inj_3_kh.collection.normalized_part_array(inj_3_kh.part, use_logarithm = True) 44 | 45 | When to use PropertyCollection and when Property 46 | ------------------------------------------------ 47 | The main advantage of the PropertyCollection class is that it allows identification of individual property arrays or groups of property arrays based on the metadata items. In particular, the property kind and facet information is a preferable way to track down a particular property than relying on the citation title. 48 | 49 | On the other hand, once the uuid for a particular property has been established, it can be passed around in code and used to instantiate an individual Property object when required. This is simpler than having to deal with a PropertyCollection once the individual uuid is known. 50 | -------------------------------------------------------------------------------- /docs/tutorial/tutorial_index.rst: -------------------------------------------------------------------------------- 1 | Tutorials 2 | ========= 3 | 4 | **These pages are currently under development.** However, the 5 | material so far in place should be usable. 6 | 7 | This page contains tutorials for programmers wanting to use the 8 | **resqpy** RESQML application programming interface (API). 9 | RESQML is the standard format for storing subsurface models. The 10 | resqpy API is a Python library providing classes and functions 11 | allowing application code to work with RESQML datasets at a 12 | high level. 13 | 14 | Whilst reading through a tutorial, it is recommended 15 | that you have a Python interpreter (or Jupyter notebook) open 16 | in another window, so that code snippets can be cut, pasted and 17 | executed whilst progressing through the material. 18 | 19 | .. toctree:: 20 | :maxdepth: 1 21 | :caption: Tutorials 22 | 23 | intro_to_resqml 24 | getting_started_with_model 25 | working_with_coord 26 | high_level_objects 27 | attributes_tutorial 28 | cellular_grids 29 | grid_properties 30 | single_property 31 | units_of_measure 32 | well_basics 33 | surfaces 34 | creating_test 35 | working_with_equinor_pyscal 36 | multiprocessing 37 | -------------------------------------------------------------------------------- /example_data/block.epc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/example_data/block.epc -------------------------------------------------------------------------------- /example_data/block.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/example_data/block.h5 -------------------------------------------------------------------------------- /example_data/s_bend.epc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/example_data/s_bend.epc -------------------------------------------------------------------------------- /example_data/s_bend.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/example_data/s_bend.h5 -------------------------------------------------------------------------------- /example_data/tic_tac_toe.epc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/example_data/tic_tac_toe.epc -------------------------------------------------------------------------------- /example_data/tic_tac_toe.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/example_data/tic_tac_toe.h5 -------------------------------------------------------------------------------- /resqpy/__init__.py: -------------------------------------------------------------------------------- 1 | """RESQML manipulation library. 2 | 3 | .. autosummary:: 4 | :toctree: _autosummary 5 | :caption: API Reference 6 | :template: custom-module-template.rst 7 | :recursive: 8 | 9 | model 10 | crs 11 | derived_model 12 | fault 13 | grid 14 | grid_surface 15 | lines 16 | multi_processing 17 | organize 18 | property 19 | rq_import 20 | strata 21 | surface 22 | time_series 23 | unstructured 24 | weights_and_measures 25 | well 26 | olio 27 | """ 28 | 29 | import logging 30 | 31 | __version__ = "0.0.0" # Set at build time 32 | log = logging.getLogger(__name__) 33 | log.info(f"Imported resqpy version {__version__}") 34 | -------------------------------------------------------------------------------- /resqpy/derived_model/__init__.py: -------------------------------------------------------------------------------- 1 | """Creating a derived resqml model from an existing one; mostly grid manipulations.""" 2 | 3 | __all__ = [ 4 | 'add_edges_per_column_property_array', 'add_faults', 'add_one_blocked_well_property', 'add_one_grid_property_array', 5 | 'add_single_cell_grid', 'add_wells_from_ascii_file', 'add_zone_by_layer_property', 'coarsened_grid', 'copy_grid', 6 | 'drape_to_surface', 'extract_box_for_well', 'extract_box', 'fault_throw_scaling', 'global_fault_throw_scaling', 7 | 'gather_ensemble', 'interpolated_grid', 'local_depth_adjustment', 'refined_grid', 'tilted_grid', 'unsplit_grid', 8 | 'zonal_grid', 'single_layer_grid', 'zone_layer_ranges_from_array' 9 | ] 10 | 11 | from ._add_edges_per_column_property_array import add_edges_per_column_property_array 12 | from ._add_faults import add_faults 13 | from ._add_one_blocked_well_property import add_one_blocked_well_property 14 | from ._add_one_grid_property_array import add_one_grid_property_array 15 | from ._add_single_cell_grid import add_single_cell_grid 16 | from ._add_wells_from_ascii_file import add_wells_from_ascii_file 17 | from ._add_zone_by_layer_property import add_zone_by_layer_property 18 | from ._coarsened_grid import coarsened_grid 19 | from ._copy_grid import copy_grid 20 | from ._drape_to_surface import drape_to_surface 21 | from ._extract_box_for_well import extract_box_for_well 22 | from ._extract_box import extract_box 23 | from ._fault_throw_scaling import fault_throw_scaling, global_fault_throw_scaling 24 | from ._gather_ensemble import gather_ensemble 25 | from ._interpolated_grid import interpolated_grid 26 | from ._local_depth_adjustment import local_depth_adjustment 27 | from ._refined_grid import refined_grid 28 | from ._tilted_grid import tilted_grid 29 | from ._unsplit_grid import unsplit_grid 30 | from ._zonal_grid import zonal_grid, single_layer_grid 31 | from ._zone_layer_ranges_from_array import zone_layer_ranges_from_array 32 | 33 | # Set "module" attribute of all public objects to this path. 34 | for _name in __all__: 35 | _obj = eval(_name) 36 | if hasattr(_obj, "__module__"): 37 | _obj.__module__ = __name__ 38 | -------------------------------------------------------------------------------- /resqpy/derived_model/_add_single_cell_grid.py: -------------------------------------------------------------------------------- 1 | """High level add single cell grid function.""" 2 | 3 | import os 4 | import numpy as np 5 | 6 | import resqpy.olio.grid_functions as gf 7 | import resqpy.rq_import as rqi 8 | 9 | 10 | def add_single_cell_grid(points, 11 | new_grid_title = None, 12 | new_epc_file = None, 13 | xy_units = 'm', 14 | z_units = 'm', 15 | z_inc_down = True): 16 | """Creates a model with a single cell IJK Grid, with a cuboid cell aligned with x,y,z axes, enclosing points.""" 17 | 18 | assert new_epc_file is not None 19 | 20 | # determine range of points 21 | min_xyz = np.nanmin(points.reshape((-1, 3)), axis = 0) 22 | max_xyz = np.nanmax(points.reshape((-1, 3)), axis = 0) 23 | assert not np.any(np.isnan(min_xyz)) and not np.any(np.isnan(max_xyz)) 24 | 25 | # create corner point array in pagoda protocol 26 | cp = np.array([[min_xyz[0], min_xyz[1], min_xyz[2]], [max_xyz[0], min_xyz[1], min_xyz[2]], 27 | [min_xyz[0], max_xyz[1], min_xyz[2]], [max_xyz[0], max_xyz[1], min_xyz[2]], 28 | [min_xyz[0], min_xyz[1], max_xyz[2]], [max_xyz[0], min_xyz[1], max_xyz[2]], 29 | [min_xyz[0], max_xyz[1], max_xyz[2]], [max_xyz[0], max_xyz[1], max_xyz[2]]]).reshape( 30 | (1, 1, 1, 2, 2, 2, 3)) 31 | 32 | # switch to nexus ordering 33 | gf.resequence_nexus_corp(cp) 34 | 35 | # write cp to temp pure binary file 36 | temp_file = new_epc_file[:-4] + '.temp.db' 37 | with open(temp_file, 'wb') as fp: 38 | fp.write(cp.data) 39 | 40 | # use_rq_import to create a new model 41 | one_cell_model = rqi.import_nexus(new_epc_file[:-4], 42 | extent_ijk = (1, 1, 1), 43 | corp_file = temp_file, 44 | corp_xy_units = xy_units, 45 | corp_z_units = z_units, 46 | corp_z_inc_down = z_inc_down, 47 | ijk_handedness = 'left', 48 | resqml_xy_units = xy_units, 49 | resqml_z_units = z_units, 50 | resqml_z_inc_down = z_inc_down, 51 | use_binary = True, 52 | split_pillars = False, 53 | grid_title = new_grid_title) 54 | grid = one_cell_model.grid() 55 | 56 | os.remove(temp_file) 57 | 58 | return grid 59 | -------------------------------------------------------------------------------- /resqpy/fault/__init__.py: -------------------------------------------------------------------------------- 1 | """Grid Connection Set class and related functions.""" 2 | 3 | __all__ = [ 4 | 'GridConnectionSet', 'pinchout_connection_set', 'k_gap_connection_set', 'cell_set_skin_connection_set', 5 | 'add_connection_set_and_tmults', 'grid_columns_property_from_gcs_property', 'zero_base_cell_indices_in_faces_df', 6 | 'standardize_face_indicator_in_faces_df', 'remove_external_faces_from_faces_df', 'combined_tr_mult_from_gcs_mults' 7 | ] 8 | 9 | from ._grid_connection_set import GridConnectionSet 10 | from ._gcs_functions import pinchout_connection_set, k_gap_connection_set, \ 11 | cell_set_skin_connection_set, add_connection_set_and_tmults, \ 12 | grid_columns_property_from_gcs_property, zero_base_cell_indices_in_faces_df, \ 13 | standardize_face_indicator_in_faces_df, remove_external_faces_from_faces_df, \ 14 | combined_tr_mult_from_gcs_mults 15 | 16 | # Set "module" attribute of all public objects to this path. 17 | for _name in __all__: 18 | _obj = eval(_name) 19 | if hasattr(_obj, "__module__"): 20 | _obj.__module__ = __name__ 21 | -------------------------------------------------------------------------------- /resqpy/grid/__init__.py: -------------------------------------------------------------------------------- 1 | """The Grid Module.""" 2 | 3 | __all__ = [ 4 | 'Grid', 'RegularGrid', 'extract_grid_parent', 'find_cell_for_x_sect_xz', 'grid_flavour', 'is_regular_grid', 5 | 'any_grid' 6 | ] 7 | 8 | from ._grid import Grid 9 | from ._regular_grid import RegularGrid 10 | from ._grid_types import grid_flavour, is_regular_grid, any_grid 11 | from ._extract_functions import extract_grid_parent, extent_kji_from_root 12 | from ._points_functions import find_cell_for_x_sect_xz 13 | 14 | # Set "module" attribute of all public objects to this path. 15 | for _name in __all__: 16 | _obj = eval(_name) 17 | if hasattr(_obj, "__module__"): 18 | _obj.__module__ = __name__ 19 | -------------------------------------------------------------------------------- /resqpy/grid/_intervals_info.py: -------------------------------------------------------------------------------- 1 | """Submodule containing the IntervalsInfo class.""" 2 | 3 | 4 | class IntervalsInfo: 5 | """A class representing intervals""" 6 | 7 | def __init__(self): 8 | """Declaring the properties of the Intervals Info class""" 9 | self.child_count_per_interval = None 10 | self.parent_count_per_interval = None 11 | self.child_cell_weights = None 12 | -------------------------------------------------------------------------------- /resqpy/grid_surface/__init__.py: -------------------------------------------------------------------------------- 1 | """Classes for RESQML objects related to surfaces.""" 2 | 3 | __all__ = [ 4 | "GridSkin", "generate_untorn_surface_for_layer_interface", "generate_torn_surface_for_layer_interface", 5 | "generate_torn_surface_for_x_section", "generate_untorn_surface_for_x_section", "point_is_within_cell", 6 | "create_column_face_mesh_and_surface", "find_intersections_of_trajectory_with_surface", 7 | "find_intersections_of_trajectory_with_layer_interface", "find_first_intersection_of_trajectory_with_surface", 8 | "find_first_intersection_of_trajectory_with_layer_interface", 9 | "find_first_intersection_of_trajectory_with_cell_surface", 10 | "find_intersection_of_trajectory_interval_with_column_face", "trajectory_grid_overlap", 11 | "populate_blocked_well_from_trajectory", "generate_surface_for_blocked_well_cells", 12 | "find_faces_to_represent_surface_staffa", "find_faces_to_represent_surface_regular", 13 | "find_faces_to_represent_surface_regular_optimised", "find_faces_to_represent_surface_regular_dense_optimised", 14 | "find_faces_to_represent_surface", "bisector_from_faces", "bisector_from_face_indices", 15 | "packed_bisector_from_face_indices", "column_bisector_from_faces", "shadow_from_faces", "get_boundary", 16 | "get_boundary_dict", "_where_true", "_first_true", "intersect_numba", "get_box" 17 | ] 18 | 19 | from ._grid_skin import GridSkin 20 | from ._grid_surface import ( 21 | generate_untorn_surface_for_layer_interface, 22 | generate_torn_surface_for_layer_interface, 23 | generate_torn_surface_for_x_section, 24 | generate_untorn_surface_for_x_section, 25 | point_is_within_cell, 26 | create_column_face_mesh_and_surface, 27 | ) 28 | from ._trajectory_intersects import ( 29 | find_intersections_of_trajectory_with_surface, 30 | find_intersections_of_trajectory_with_layer_interface, 31 | find_first_intersection_of_trajectory_with_surface, 32 | find_first_intersection_of_trajectory_with_layer_interface, 33 | find_first_intersection_of_trajectory_with_cell_surface, 34 | find_intersection_of_trajectory_interval_with_column_face, 35 | trajectory_grid_overlap, 36 | ) 37 | from ._blocked_well_populate import ( 38 | populate_blocked_well_from_trajectory, 39 | generate_surface_for_blocked_well_cells, 40 | ) 41 | from ._find_faces import (find_faces_to_represent_surface_staffa, find_faces_to_represent_surface_regular, 42 | find_faces_to_represent_surface_regular_optimised, 43 | find_faces_to_represent_surface_regular_dense_optimised, find_faces_to_represent_surface, 44 | bisector_from_faces, bisector_from_face_indices, packed_bisector_from_face_indices, 45 | column_bisector_from_faces, shadow_from_faces, get_boundary, get_boundary_dict, _where_true, 46 | _first_true, intersect_numba, get_box) 47 | 48 | # Set "module" attribute of all public objects to this path. 49 | for _name in __all__: 50 | _obj = eval(_name) 51 | if hasattr(_obj, "__module__"): 52 | _obj.__module__ = __name__ 53 | -------------------------------------------------------------------------------- /resqpy/lines/__init__.py: -------------------------------------------------------------------------------- 1 | """Polyline and PolylineSet classes and associated functions.""" 2 | 3 | __all__ = ['Polyline', 'PolylineSet', 'load_hdf5_array', 'shift_polyline', 'flatten_polyline', 'tangents', 'spline'] 4 | 5 | from ._common import load_hdf5_array, shift_polyline, flatten_polyline, tangents, spline 6 | from ._polyline import Polyline 7 | from ._polyline_set import PolylineSet 8 | 9 | # Set "module" attribute of all public objects to this path. Fixes issue #310 10 | for _name in __all__: 11 | _obj = eval(_name) 12 | if hasattr(_obj, "__module__"): 13 | _obj.__module__ = __name__ 14 | -------------------------------------------------------------------------------- /resqpy/model/__init__.py: -------------------------------------------------------------------------------- 1 | """Model class, roughly equivalent to a RESQML epc file.""" 2 | 3 | __all__ = ['Model', 'ModelContext', 'new_model'] 4 | 5 | from ._model import Model, new_model 6 | from ._context import ModelContext 7 | 8 | # Set "module" attribute of all public objects to this path. Fixes issue #310 9 | for _name in __all__: 10 | _obj = eval(_name) 11 | if hasattr(_obj, "__module__"): 12 | _obj.__module__ = __name__ 13 | -------------------------------------------------------------------------------- /resqpy/model/_context.py: -------------------------------------------------------------------------------- 1 | """_context.py: ModelContext class.""" 2 | 3 | import logging 4 | 5 | log = logging.getLogger(__name__) 6 | 7 | import os 8 | from typing import Optional 9 | 10 | import resqpy.model 11 | from resqpy.model import Model, new_model 12 | 13 | 14 | class ModelContext: 15 | """Context manager for easy opening and closing of resqpy models. 16 | 17 | When a model is opened this way, any open file handles are safely closed 18 | when the "with" clause exits. Optionally, the epc can be written back to 19 | disk upon exit. 20 | 21 | Example:: 22 | 23 | with ModelContext("my_model.epc", mode="rw") as model: 24 | print(model.uuids()) 25 | 26 | Note: 27 | The "write_hdf5" and "create_xml" methods of individual resqpy objects 28 | still need to be invoked as usual. 29 | """ 30 | 31 | def __init__(self, epc_file, mode = "r") -> None: 32 | """Open a resqml file, safely closing file handles upon exit. 33 | 34 | arguments: 35 | epc_file (str): path to existing resqml file 36 | mode (str, default 'r'): one of "read", "read/write", "create", or shorthands "r", "rw", "c" 37 | 38 | notes: 39 | the modes operate as follows: 40 | - In "read" mode, an existing epc file is opened; any changes are not 41 | saved to disk automatically, but can still be saved by calling 42 | `model.store_epc()`; 43 | - In "read/write" mode, changes are written to disk when the context exists; 44 | - In "create" mode, a new model is created and saved upon exit; any pre-existing 45 | model will be deleted 46 | """ 47 | 48 | # Validate mode 49 | modes_mapping = {"r": "read", "rw": "read/write", "c": "create"} 50 | mode = modes_mapping.get(mode, mode) 51 | if mode not in modes_mapping.values(): 52 | raise ValueError(f"Unexpected mode '{mode}'") 53 | 54 | self.epc_file = epc_file 55 | self.mode = mode 56 | self._model: Optional[Model] = None 57 | 58 | def __enter__(self) -> Model: 59 | """Enter the runtime context, return a model.""" 60 | 61 | if self.mode in ["read", "read/write"]: 62 | if not os.path.exists(self.epc_file): 63 | raise FileNotFoundError(self.epc_file) 64 | self._model = Model(epc_file = str(self.epc_file)) 65 | 66 | else: 67 | assert self.mode == "create" 68 | for file in [self.epc_file, self.epc_file[:-4] + '.h5']: 69 | if os.path.exists(file): 70 | os.remove(file) 71 | log.info('old file deleted: ' + str(file)) 72 | self._model = new_model(self.epc_file) 73 | 74 | return self._model 75 | 76 | def __exit__(self, exc_type, exc_value, exc_tb): 77 | """Exit the runtime context, close the model.""" 78 | 79 | # Only write to disk if no exception has occured 80 | if self.mode in ["read/write", "create"] and exc_type is None: 81 | self._model.store_epc() 82 | 83 | # Release file handles 84 | self._model.h5_release() 85 | -------------------------------------------------------------------------------- /resqpy/model/_grids.py: -------------------------------------------------------------------------------- 1 | """_grids.py: functions supporting Model methods relating to grid objects.""" 2 | 3 | import resqpy.grid as grr 4 | import resqpy.olio.uuid as bu 5 | import resqpy.olio.xml_et as rqet 6 | 7 | 8 | def _root_for_ijk_grid(model, uuid = None, title = None): 9 | """Return xml root for IJK Grid part.""" 10 | 11 | if title is not None: 12 | title = title.strip().upper() 13 | if uuid is None and not title: 14 | grid_root = model.root(obj_type = 'IjkGridRepresentation', title = 'ROOT', multiple_handling = 'oldest') 15 | if grid_root is None: 16 | grid_root = model.root(obj_type = 'IjkGridRepresentation') 17 | else: 18 | grid_root = model.root(obj_type = 'IjkGridRepresentation', uuid = uuid, title = title) 19 | 20 | assert grid_root is not None, 'IJK Grid part not found' 21 | 22 | return grid_root 23 | 24 | 25 | def _resolve_grid_root(model, grid_root = None, uuid = None): 26 | """If grid root argument is None, returns the xml root for the IJK Grid part instead.""" 27 | 28 | if grid_root is not None: 29 | if model.grid_root is None: 30 | model.grid_root = grid_root 31 | else: 32 | if model.grid_root is None: 33 | model.grid_root = _root_for_ijk_grid(model, uuid = uuid) 34 | grid_root = model.grid_root 35 | return grid_root 36 | 37 | 38 | def _grid(model, title = None, uuid = None, find_properties = True): 39 | """Returns a shared Grid (or RegularGrid) object for this model, by default the 'main' grid.""" 40 | 41 | if uuid is None and (title is None or title.upper() == 'ROOT'): 42 | if model.main_grid is not None: 43 | if find_properties: 44 | model.main_grid.extract_property_collection() 45 | return model.main_grid 46 | if title is None: 47 | grid_root = _resolve_grid_root(model) 48 | else: 49 | grid_root = _resolve_grid_root(model, 50 | grid_root = model.root(obj_type = 'IjkGridRepresentation', title = title)) 51 | else: 52 | grid_root = model.root(obj_type = 'IjkGridRepresentation', uuid = uuid, title = title) 53 | assert grid_root is not None, 'IJK Grid part not found' 54 | if uuid is None: 55 | uuid = rqet.uuid_for_part_root(grid_root) 56 | for grid in model.grid_list: 57 | if grid.root is grid_root: 58 | if find_properties: 59 | grid.extract_property_collection() 60 | return grid 61 | grid = grr.any_grid(model, uuid = uuid, find_properties = find_properties) 62 | assert grid is not None, 'failed to instantiate grid object' 63 | if find_properties: 64 | grid.extract_property_collection() 65 | _add_grid(model, grid) 66 | return grid 67 | 68 | 69 | def _add_grid(model, grid_object, check_for_duplicates = False): 70 | """Add grid object to list of shareable grids for this model.""" 71 | 72 | if check_for_duplicates: 73 | for g in model.grid_list: 74 | if bu.matching_uuids(g.uuid, grid_object.uuid): 75 | return 76 | model.grid_list.append(grid_object) 77 | 78 | 79 | def _grid_list_uuid_list(model): 80 | """Returns list of uuid's for the grid objects in the cached grid list.""" 81 | 82 | uuid_list = [] 83 | for grid in model.grid_list: 84 | uuid_list.append(grid.uuid) 85 | return uuid_list 86 | 87 | 88 | def _grid_for_uuid_from_grid_list(model, uuid): 89 | """Returns the cached grid object matching the given uuid, if found in the grid list, otherwise None.""" 90 | 91 | for grid in model.grid_list: 92 | if bu.matching_uuids(uuid, grid.uuid): 93 | return grid 94 | return None 95 | -------------------------------------------------------------------------------- /resqpy/multi_processing/__init__.py: -------------------------------------------------------------------------------- 1 | """Multiprocessing module for running specific functions concurrently.""" 2 | 3 | __all__ = [ 4 | 'function_multiprocessing', 'find_faces_to_represent_surface_regular_wrapper', 5 | 'mesh_from_regular_grid_column_property_wrapper', 'mesh_from_regular_grid_column_property_batch', 6 | 'blocked_well_from_trajectory_wrapper', 'blocked_well_from_trajectory_batch' 7 | ] 8 | 9 | from ._multiprocessing import function_multiprocessing 10 | from .wrappers.grid_surface_mp import find_faces_to_represent_surface_regular_wrapper 11 | from .wrappers.mesh_mp import mesh_from_regular_grid_column_property_wrapper, mesh_from_regular_grid_column_property_batch 12 | from .wrappers.blocked_well_mp import blocked_well_from_trajectory_wrapper, blocked_well_from_trajectory_batch 13 | 14 | # Set "module" attribute of all public objects to this path. 15 | for _name in __all__: 16 | _obj = eval(_name) 17 | if hasattr(_obj, "__module__"): 18 | _obj.__module__ = __name__ 19 | -------------------------------------------------------------------------------- /resqpy/multi_processing/wrappers/__init__.py: -------------------------------------------------------------------------------- 1 | """Wrapper module for the wrapper functions used in multiprocessing.""" 2 | -------------------------------------------------------------------------------- /resqpy/olio/__init__.py: -------------------------------------------------------------------------------- 1 | """Low level supporting modules, mostly providing functions rather than classes.""" 2 | -------------------------------------------------------------------------------- /resqpy/olio/ab_toolbox.py: -------------------------------------------------------------------------------- 1 | """Small utility functions related to use of pure binary files.""" 2 | 3 | # Nexus is a registered trademark of the Halliburton Company 4 | 5 | import logging 6 | 7 | log = logging.getLogger(__name__) 8 | 9 | import numpy as np 10 | from typing import Tuple, Optional 11 | 12 | ab_dtype_dict = {'.db': np.float64, '.fb': np.float32, '.lb': np.int64, '.ib': np.int32, '.bb': np.int8} 13 | 14 | 15 | def load_array_from_ab_file(file_name, shape, return_64_bit = False): 16 | """Loads a pure binary file into a numpy array, optionally converting to 64 bit.""" 17 | 18 | count = 1 19 | for axis in range(len(shape)): 20 | count *= shape[axis] 21 | dtype = ab_dtype_dict[file_name[-3:]] 22 | with open(file_name, 'rb') as fp: 23 | a = np.fromfile(fp, dtype = dtype, count = count).reshape(tuple(shape)) 24 | try: # expected to return null 25 | c = fp.read(1) 26 | if len(c): 27 | log.warning('binary file contains more data than expected: ' + file_name) 28 | except Exception: 29 | pass 30 | return a 31 | 32 | 33 | def cp_binary_filename(file_name, nexus_ordering = True): 34 | """Returns a version of the file name with extension adjusted to indicate reseq order and pure binary.""" 35 | 36 | if file_name[-9:] == '.reseq.db': 37 | root_name = file_name[:-9] 38 | elif file_name[-3:] == '.db': 39 | root_name = file_name[:-3] 40 | else: 41 | root_name = file_name 42 | if nexus_ordering: 43 | return root_name + '.db' 44 | else: 45 | return root_name + '.reseq.db' 46 | 47 | 48 | def binary_file_extension_and_np_type_for_data_type(data_type: str) -> Optional[Tuple[str, object]]: 49 | """Returns a file extension suitable for a pure binary array (ab) file of given data type.""" 50 | 51 | binary_file_ext_and_type = { 52 | 'real': ('.db', np.dtype('f8')), 53 | 'float': ('.db', np.dtype('f8')), 54 | 'int': ( 55 | '.lb', 56 | np.dtype('i8'), 57 | ), 58 | 'integer': ('.lb', np.dtype('i8')), 59 | 'bool': ('.bb', np.dtype('?')), 60 | 'boolean': ('.bb', np.dtype('?')) 61 | } 62 | 63 | try: 64 | return binary_file_ext_and_type.get(data_type) 65 | except KeyError: 66 | log.error(f'Unknown data_type [{data_type}] passed to binary_file_extension_and_np_type_for_data_type') 67 | raise 68 | -------------------------------------------------------------------------------- /resqpy/olio/exceptions.py: -------------------------------------------------------------------------------- 1 | """Custom exceptions used in resqpy.""" 2 | 3 | 4 | class InvalidUnitError(Exception): 5 | """Raised when a unit cannot be converted into a valid RESQML unit of measure.""" 6 | pass 7 | 8 | 9 | class IncompatibleUnitsError(Exception): 10 | """Raised when two units do not share compatible base units and dimensions.""" 11 | pass 12 | -------------------------------------------------------------------------------- /resqpy/olio/factors.py: -------------------------------------------------------------------------------- 1 | """Factorization and functions supporting grid extent determination from corner points.""" 2 | 3 | 4 | def factorize(n): 5 | """Returns list of prime factors of positive integer n.""" 6 | i = 2 7 | factors = [] 8 | while True: 9 | q, r = divmod(n, i) 10 | if r: 11 | i += 1 12 | if i > n: 13 | return factors 14 | else: 15 | factors.append(i) 16 | if q < i: 17 | return factors 18 | n = q 19 | 20 | 21 | def combinatorial(numbers): 22 | """Returns a list of all possible product combinations of numbers from list numbers, with some duplicates.""" 23 | if len(numbers) == 0: 24 | return [] 25 | head = numbers[0] 26 | c = [head] 27 | tail = combinatorial(numbers[1:]) 28 | for o in tail: 29 | if o != head: 30 | c.append(o) 31 | c.append(head * o) 32 | return sorted(c) 33 | 34 | 35 | def all_factors_from_primes(primes): 36 | """Returns a sorted list of unique factors from prime factorization.""" 37 | all = list(set(combinatorial(primes))) 38 | all.append(1) 39 | return sorted(all) 40 | 41 | 42 | def all_factors(n): 43 | """Returns a sorted list of unique factors of n.""" 44 | primes = factorize(n) 45 | return all_factors_from_primes(primes) 46 | 47 | 48 | def remove_subset(primary, subset): 49 | """Remove all elements of subset from primary list.""" 50 | 51 | for e in subset: 52 | primary.remove(e) 53 | -------------------------------------------------------------------------------- /resqpy/olio/random_seed.py: -------------------------------------------------------------------------------- 1 | """Module providing wrapper for random number generator seeding functions.""" 2 | 3 | import numpy 4 | import random 5 | 6 | 7 | def seed(seed, package = 'all'): 8 | """Set seed for random number generator of one or more packages, to allow for repeatable behaviour. 9 | 10 | arguments: 11 | seed (int): the value to use to seed the random number generator(s); a value of None will 12 | generally result in an unrepeatable sequence 13 | package (string or list of strings): one or more of known packages: 'random' and 'numpy' at present; 14 | passing 'all' will cause all packages known to have a random number generator to be re-seeded 15 | """ 16 | 17 | known_list = ['random', 'numpy'] 18 | 19 | if isinstance(package, str): 20 | if package == 'all': 21 | package = known_list 22 | else: 23 | package = [package] 24 | 25 | assert isinstance(package, list) 26 | 27 | for pack in package: 28 | assert pack in known_list, 'unknown package for random number seeding: ' + str(pack) 29 | 30 | if pack == 'random': 31 | random.seed(seed) 32 | elif pack == 'numpy': 33 | numpy.random.seed(seed = seed) 34 | -------------------------------------------------------------------------------- /resqpy/olio/time.py: -------------------------------------------------------------------------------- 1 | """time.py: A very thin wrapper around python datetime functionality, to meet resqml standard.""" 2 | 3 | import datetime as dt 4 | 5 | 6 | def now(use_utc = False): 7 | """Returns an iso format string representation of the current time, to the nearest second. 8 | 9 | argument: 10 | use_utc (boolean, default False): if True, the current UTC time is used, otherwise local time 11 | 12 | returns: 13 | string of form YYYY-MM-DDThh:mm:ssZ representing the current time in iso format 14 | 15 | note: 16 | this is the format used by the resqml standard for representing date-times 17 | """ 18 | 19 | if use_utc: 20 | stamp = dt.datetime.utcnow() # NB: naive use of UTC time 21 | else: 22 | stamp = dt.datetime.now() 23 | return str(stamp.isoformat(sep = 'T', timespec = 'seconds')) + 'Z' 24 | -------------------------------------------------------------------------------- /resqpy/olio/trademark.py: -------------------------------------------------------------------------------- 1 | """trademark.py module for mentioning trademarks in diagnostic log.""" 2 | 3 | # Nexus is a registered trademark of the Halliburton Company 4 | 5 | import logging as lg 6 | 7 | log = lg.getLogger(__name__) 8 | 9 | nexus_tm_level = None 10 | 11 | 12 | def log_nexus_tm(level = lg.INFO): 13 | """Produces a Nexus trademark log message once at the given severity. 14 | 15 | note: 16 | this function should be called after referring to Nexus in another log message, passing the 17 | severity of that other message 18 | """ 19 | 20 | global nexus_tm_level 21 | if isinstance(level, str): 22 | level = lg.__dict__[level.upper()] 23 | if nexus_tm_level is None or level > nexus_tm_level: 24 | preamble = '(ignore severity) ' if level > 20 else '' 25 | log.log(level, preamble + 'Nexus is a registered trademark of the Halliburton Company') 26 | nexus_tm_level = level 27 | -------------------------------------------------------------------------------- /resqpy/olio/xml_namespaces.py: -------------------------------------------------------------------------------- 1 | """xml_namespaces.py: Module defining constant resqml xml namespaces.""" 2 | 3 | # major revamp to support bespoke serialisation code, needed to emulate gSOAP, hence FESAPI 4 | 5 | namespace = {} 6 | 7 | namespace['xsd'] = 'http://www.w3.org/2001/XMLSchema' 8 | namespace['xsi'] = 'http://www.w3.org/2001/XMLSchema-instance' 9 | # namespace['schema'] = 'http://www.w3.org/2001/XMLSchema-instance' 10 | 11 | namespace['eml'] = 'http://www.energistics.org/energyml/data/commonv2' 12 | namespace['eml20'] = 'http://www.energistics.org/energyml/data/commonv2' 13 | namespace['eml23'] = 'http://www.energistics.org/energyml/data/commonv2' 14 | namespace['resqml2'] = 'http://www.energistics.org/energyml/data/resqmlv2' 15 | namespace['resqml'] = 'http://www.energistics.org/energyml/data/resqmlv2' 16 | namespace['resqml20'] = 'http://www.energistics.org/energyml/data/resqmlv2' 17 | namespace['rels_ext'] = 'http://schemas.energistics.org/package/2012/relationships/' 18 | 19 | namespace['rels'] = 'http://schemas.openxmlformats.org/package/2006/relationships' 20 | namespace['rels_md'] = 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/' 21 | 22 | namespace['content_types'] = 'http://schemas.openxmlformats.org/package/2006/content-types' 23 | namespace['cp'] = 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties' 24 | 25 | namespace['dcterms'] = 'http://purl.org/dc/terms/' 26 | namespace['dc'] = 'http://purl.org/dc/elements/1.1/' 27 | 28 | curly_namespace = {} 29 | for key, url in namespace.items(): 30 | curly_namespace[key] = '{' + url + '}' 31 | 32 | inverse_namespace = {} 33 | for key, url in namespace.items(): 34 | if url not in inverse_namespace: 35 | inverse_namespace[url] = key 36 | 37 | 38 | def colon_namespace(url): 39 | """Returns the short form namespace for the url, complete with colon suffix.""" 40 | 41 | if url[0] == '{': 42 | return inverse_namespace[url[1:-1]] + ':' 43 | return inverse_namespace[url] + ':' 44 | -------------------------------------------------------------------------------- /resqpy/organize/__init__.py: -------------------------------------------------------------------------------- 1 | """Organizational object classes: features and interpretations.""" 2 | 3 | __all__ = [ 4 | # Classes 5 | "BoundaryFeature", 6 | "BoundaryFeatureInterpretation", 7 | "EarthModelInterpretation", 8 | "FaultInterpretation", 9 | "FluidBoundaryFeature", 10 | "FrontierFeature", 11 | "GenericInterpretation", 12 | "GeneticBoundaryFeature", 13 | "GeobodyBoundaryInterpretation", 14 | "GeobodyFeature", 15 | "GeobodyInterpretation", 16 | "GeologicUnitFeature", 17 | "HorizonInterpretation", 18 | "OrganizationFeature", 19 | "RockFluidUnitFeature", 20 | "TectonicBoundaryFeature", 21 | "WellboreFeature", 22 | "WellboreInterpretation", 23 | # Functions 24 | "equivalent_extra_metadata", 25 | "alias_for_attribute", 26 | "extract_has_occurred_during", 27 | "equivalent_chrono_pairs", 28 | "create_xml_has_occurred_during", 29 | ] 30 | 31 | from resqpy.organize._utils import (equivalent_extra_metadata, alias_for_attribute, extract_has_occurred_during, 32 | equivalent_chrono_pairs, create_xml_has_occurred_during) 33 | from .boundary_feature import BoundaryFeature 34 | from .boundary_feature_interpretation import BoundaryFeatureInterpretation 35 | from .earth_model_interpretation import EarthModelInterpretation 36 | from .fault_interpretation import FaultInterpretation 37 | from .fluid_boundary_feature import FluidBoundaryFeature 38 | from .frontier_feature import FrontierFeature 39 | from .generic_interpretation import GenericInterpretation 40 | from .genetic_boundary_feature import GeneticBoundaryFeature 41 | from .geobody_boundary_interpretation import GeobodyBoundaryInterpretation 42 | from .geobody_feature import GeobodyFeature 43 | from .geobody_interpretation import GeobodyInterpretation 44 | from .geologic_unit_feature import GeologicUnitFeature 45 | from .horizon_interpretation import HorizonInterpretation 46 | from .organization_feature import OrganizationFeature 47 | from .rock_fluid_unit_feature import RockFluidUnitFeature 48 | from .tectonic_boundary_feature import TectonicBoundaryFeature 49 | from .wellbore_feature import WellboreFeature 50 | from .wellbore_interpretation import WellboreInterpretation 51 | 52 | # Set "module" attribute of all public objects to this path. 53 | for _name in __all__: 54 | _obj = eval(_name) 55 | if hasattr(_obj, "__module__"): 56 | _obj.__module__ = __name__ 57 | -------------------------------------------------------------------------------- /resqpy/organize/_utils.py: -------------------------------------------------------------------------------- 1 | """Helper functions for RESQML Feature and Interpretation classes.""" 2 | 3 | import resqpy.olio.xml_et as rqet 4 | from resqpy.olio.xml_namespaces import curly_namespace as ns 5 | 6 | 7 | def alias_for_attribute(attribute_name): 8 | """Return an attribute that is a direct alias for an existing attribute.""" 9 | 10 | def fget(self): 11 | return getattr(self, attribute_name) 12 | 13 | def fset(self, value): 14 | return setattr(self, attribute_name, value) 15 | 16 | return property(fget, fset, doc = f"Alias for {attribute_name}") 17 | 18 | 19 | def equivalent_extra_metadata(a, b): 20 | """Returns True if the two objects have identical extra metadata""" 21 | a_has = hasattr(a, 'extra_metadata') 22 | b_has = hasattr(b, 'extra_metadata') 23 | if a_has: 24 | a_em = a.extra_metadata 25 | a_has = len(a_em) > 0 26 | else: 27 | a_em = rqet.load_metadata_from_xml(a.root) 28 | a_has = a_em is not None and len(a_em) > 0 29 | if b_has: 30 | b_em = b.extra_metadata 31 | b_has = len(b_em) > 0 32 | else: 33 | b_em = rqet.load_metadata_from_xml(b.root) 34 | b_has = b_em is not None and len(b_em) > 0 35 | if a_has != b_has: 36 | return False 37 | if not a_has: 38 | return True 39 | return a_em == b_em 40 | 41 | 42 | def extract_has_occurred_during(parent_node, tag = 'HasOccuredDuring'): # RESQML Occured (stet) 43 | """Extracts UUIDs of chrono bottom and top from xml for has occurred during sub-node, or (None, None).""" 44 | hod_node = rqet.find_tag(parent_node, tag) 45 | if hod_node is None: 46 | return (None, None) 47 | else: 48 | return (rqet.find_nested_tags_text(hod_node, ['ChronoBottom', 'UUID']), 49 | rqet.find_nested_tags_text(hod_node, ['ChronoTop', 'UUID'])) 50 | 51 | 52 | def equivalent_chrono_pairs(pair_a, pair_b, model = None): 53 | """Returns True if the two chronostratigraphic pairs are equivalent""" 54 | if pair_a == pair_b: 55 | return True 56 | if pair_a is None or pair_b is None: 57 | return False 58 | if pair_a == (None, None) or pair_b == (None, None): 59 | return False 60 | if model is not None: 61 | # todo: compare chrono info by looking up xml based on the uuids 62 | pass 63 | return False # cautious 64 | 65 | 66 | def create_xml_has_occurred_during(model, parent_node, hod_pair, tag = 'HasOccuredDuring'): 67 | """Creates XML sub-tree 'HasOccuredDuring' node""" 68 | if hod_pair is None: 69 | return 70 | base_chrono_uuid, top_chrono_uuid = hod_pair 71 | if base_chrono_uuid is None or top_chrono_uuid is None: 72 | return 73 | hod_node = rqet.SubElement(parent_node, tag) 74 | hod_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'TimeInterval') 75 | hod_node.text = rqet.null_xml_text 76 | chrono_base_root = model.root_for_uuid(base_chrono_uuid) 77 | chrono_top_root = model.root_for_uuid(top_chrono_uuid) 78 | model.create_ref_node('ChronoBottom', model.title_for_root(chrono_base_root), base_chrono_uuid, root = hod_node) 79 | model.create_ref_node('ChronoTop', model.title_for_root(chrono_top_root), top_chrono_uuid, root = hod_node) 80 | -------------------------------------------------------------------------------- /resqpy/organize/boundary_feature.py: -------------------------------------------------------------------------------- 1 | """Class for RESQML Boundary Feature organizational objects.""" 2 | 3 | import resqpy.olio.uuid as bu 4 | import resqpy.organize 5 | import resqpy.organize._utils as ou 6 | from resqpy.olio.base import BaseResqpy 7 | 8 | 9 | class BoundaryFeature(BaseResqpy): 10 | """Class for RESQML Boundary Feature organizational objects.""" 11 | 12 | resqml_type = "BoundaryFeature" 13 | feature_name = ou.alias_for_attribute("title") 14 | 15 | def __init__(self, parent_model, uuid = None, feature_name = None, originator = None, extra_metadata = None): 16 | """Initialises a boundary feature organisational object.""" 17 | 18 | super().__init__(model = parent_model, 19 | uuid = uuid, 20 | title = feature_name, 21 | originator = originator, 22 | extra_metadata = extra_metadata) 23 | 24 | def is_equivalent(self, other, check_extra_metadata = True): 25 | """Returns True if this feature is essentially the same as the other; otherwise False.""" 26 | 27 | if other is None or not isinstance(other, BoundaryFeature): 28 | return False 29 | if self is other or bu.matching_uuids(self.uuid, other.uuid): 30 | return True 31 | if check_extra_metadata and not ou.equivalent_extra_metadata(self, other): 32 | return False 33 | return self.feature_name == other.feature_name 34 | 35 | def create_xml(self, add_as_part = True, originator = None, reuse = True): 36 | """Creates a geobody feature xml node from this geobody feature object.""" 37 | if reuse and self.try_reuse(): 38 | return self.root # check for reusable (equivalent) object 39 | return super().create_xml(add_as_part = add_as_part, originator = originator) 40 | -------------------------------------------------------------------------------- /resqpy/organize/fluid_boundary_feature.py: -------------------------------------------------------------------------------- 1 | """Class for RESQML Fluid Boundary Feature (contact) organizational objects.""" 2 | 3 | import resqpy.olio.uuid as bu 4 | import resqpy.olio.xml_et as rqet 5 | import resqpy.organize 6 | import resqpy.organize._utils as ou 7 | from resqpy.olio.base import BaseResqpy 8 | from resqpy.olio.xml_namespaces import curly_namespace as ns 9 | 10 | 11 | class FluidBoundaryFeature(BaseResqpy): 12 | """Class for RESQML Fluid Boundary Feature (contact) organizational objects.""" 13 | 14 | resqml_type = "FluidBoundaryFeature" 15 | feature_name = ou.alias_for_attribute("title") 16 | valid_kinds = ('free water contact', 'gas oil contact', 'gas water contact', 'seal', 'water oil contact') 17 | 18 | def __init__(self, 19 | parent_model, 20 | uuid = None, 21 | kind = None, 22 | feature_name = None, 23 | originator = None, 24 | extra_metadata = None): 25 | """Initialises a fluid boundary feature (contact) organisational object.""" 26 | 27 | self.kind = kind 28 | super().__init__(model = parent_model, 29 | uuid = uuid, 30 | title = feature_name, 31 | originator = originator, 32 | extra_metadata = extra_metadata) 33 | 34 | def is_equivalent(self, other, check_extra_metadata = True): 35 | """Returns True if this feature is essentially the same as the other; otherwise False.""" 36 | 37 | if other is None or not isinstance(other, FluidBoundaryFeature): 38 | return False 39 | if self is other or bu.matching_uuids(self.uuid, other.uuid): 40 | return True 41 | if check_extra_metadata and not ou.equivalent_extra_metadata(self, other): 42 | return False 43 | return self.feature_name == other.feature_name and self.kind == other.kind 44 | 45 | def _load_from_xml(self): 46 | self.kind = rqet.find_tag_text(self.root, 'FluidContact') 47 | 48 | def create_xml(self, add_as_part = True, originator = None, reuse = True): 49 | """Creates a fluid boundary feature organisational xml node from this fluid boundary feature object.""" 50 | 51 | if reuse and self.try_reuse(): 52 | return self.root # check for reusable (equivalent) object 53 | # create node with citation block 54 | fbf = super().create_xml(add_as_part = False, originator = originator) 55 | 56 | # Extra element for kind 57 | if self.kind not in self.valid_kinds: 58 | raise ValueError(f"fluid boundary feature kind '{self.kind}' not recognized") 59 | 60 | kind_node = rqet.SubElement(fbf, ns['resqml2'] + 'FluidContact') 61 | kind_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'FluidContact') 62 | kind_node.text = self.kind 63 | 64 | if add_as_part: 65 | self.model.add_part('obj_FluidBoundaryFeature', self.uuid, fbf) 66 | 67 | return fbf 68 | -------------------------------------------------------------------------------- /resqpy/organize/frontier_feature.py: -------------------------------------------------------------------------------- 1 | """Class for RESQML Frontier Feature organizational objects.""" 2 | 3 | import resqpy.olio.uuid as bu 4 | import resqpy.organize 5 | import resqpy.organize._utils as ou 6 | from resqpy.olio.base import BaseResqpy 7 | 8 | 9 | class FrontierFeature(BaseResqpy): 10 | """Class for RESQML Frontier Feature organizational objects.""" 11 | 12 | resqml_type = "FrontierFeature" 13 | feature_name = ou.alias_for_attribute("title") 14 | 15 | def __init__(self, parent_model, uuid = None, feature_name = None, originator = None, extra_metadata = None): 16 | """Initialises a frontier feature organisational object.""" 17 | 18 | super().__init__(model = parent_model, 19 | uuid = uuid, 20 | title = feature_name, 21 | originator = originator, 22 | extra_metadata = extra_metadata) 23 | 24 | def is_equivalent(self, other, check_extra_metadata = True): 25 | """Returns True if this feature is essentially the same as the other; otherwise False.""" 26 | 27 | if other is None or not isinstance(other, FrontierFeature): 28 | return False 29 | if self is other or bu.matching_uuids(self.uuid, other.uuid): 30 | return True 31 | if check_extra_metadata and not ou.equivalent_extra_metadata(self, other): 32 | return False 33 | return self.feature_name == other.feature_name 34 | 35 | def create_xml(self, add_as_part = True, originator = None, reuse = True): 36 | """Creates a frontier feature organisational xml node from this frontier feature object.""" 37 | if reuse and self.try_reuse(): 38 | return self.root # check for reusable (equivalent) object 39 | return super().create_xml(add_as_part = add_as_part, originator = originator) 40 | -------------------------------------------------------------------------------- /resqpy/organize/geobody_feature.py: -------------------------------------------------------------------------------- 1 | """RESQML Feature and Interpretation classes.""" 2 | 3 | # most feature and interpretation classes catered for here 4 | # stratigraphic classes in strata.py 5 | 6 | import logging 7 | 8 | log = logging.getLogger(__name__) 9 | 10 | import resqpy.olio.uuid as bu 11 | import resqpy.organize 12 | import resqpy.organize._utils as ou 13 | from resqpy.olio.base import BaseResqpy 14 | 15 | 16 | class GeobodyFeature(BaseResqpy): 17 | """Class for RESQML Geobody Feature objects (note: definition may be incomplete in RESQML 2.0.1).""" 18 | 19 | resqml_type = "GeobodyFeature" 20 | feature_name = ou.alias_for_attribute("title") 21 | 22 | def __init__(self, parent_model, uuid = None, feature_name = None, originator = None, extra_metadata = None): 23 | """Initialises a geobody feature object.""" 24 | 25 | super().__init__(model = parent_model, 26 | uuid = uuid, 27 | title = feature_name, 28 | originator = originator, 29 | extra_metadata = extra_metadata) 30 | 31 | def is_equivalent(self, other, check_extra_metadata = True): 32 | """Returns True if this feature is essentially the same as the other; otherwise False.""" 33 | 34 | if other is None or not isinstance(other, self.__class__): 35 | return False 36 | if self is other or bu.matching_uuids(self.uuid, other.uuid): 37 | return True 38 | if check_extra_metadata and not ou.equivalent_extra_metadata(self, other): 39 | return False 40 | return self.feature_name == other.feature_name 41 | 42 | def create_xml(self, add_as_part = True, originator = None, reuse = True): 43 | """Creates a geobody feature xml node from this geobody feature object.""" 44 | if reuse and self.try_reuse(): 45 | return self.root # check for reusable (equivalent) object 46 | return super().create_xml(add_as_part = add_as_part, originator = originator) 47 | -------------------------------------------------------------------------------- /resqpy/organize/geologic_unit_feature.py: -------------------------------------------------------------------------------- 1 | """Class for RESQML Geologic Unit Feature organizational objects.""" 2 | 3 | import resqpy.olio.uuid as bu 4 | import resqpy.organize 5 | import resqpy.organize._utils as ou 6 | from resqpy.olio.base import BaseResqpy 7 | 8 | 9 | class GeologicUnitFeature(BaseResqpy): 10 | """Class for RESQML Geologic Unit Feature organizational objects.""" 11 | 12 | resqml_type = "GeologicUnitFeature" 13 | feature_name = ou.alias_for_attribute("title") 14 | 15 | def __init__(self, parent_model, uuid = None, feature_name = None, originator = None, extra_metadata = None): 16 | """Initialises a geologic unit feature organisational object.""" 17 | 18 | super().__init__(model = parent_model, 19 | uuid = uuid, 20 | title = feature_name, 21 | originator = originator, 22 | extra_metadata = extra_metadata) 23 | 24 | def is_equivalent(self, other, check_extra_metadata = True): 25 | """Returns True if this feature is essentially the same as the other; otherwise False.""" 26 | 27 | if other is None or not isinstance(other, GeologicUnitFeature): 28 | return False 29 | if self is other or bu.matching_uuids(self.uuid, other.uuid): 30 | return True 31 | if check_extra_metadata and not ou.equivalent_extra_metadata(self, other): 32 | return False 33 | return self.feature_name == other.feature_name 34 | 35 | def create_xml(self, add_as_part = True, originator = None, reuse = True): 36 | """Creates a geologic unit feature organisational xml node from this geologic unit feature object.""" 37 | if reuse and self.try_reuse(): 38 | return self.root # check for reusable (equivalent) object 39 | return super().create_xml(add_as_part = add_as_part, originator = originator) 40 | -------------------------------------------------------------------------------- /resqpy/organize/organization_feature.py: -------------------------------------------------------------------------------- 1 | """Class for generic RESQML Organization Feature objects.""" 2 | 3 | import resqpy.olio.uuid as bu 4 | import resqpy.olio.xml_et as rqet 5 | import resqpy.organize 6 | import resqpy.organize._utils as ou 7 | from resqpy.olio.base import BaseResqpy 8 | from resqpy.olio.xml_namespaces import curly_namespace as ns 9 | 10 | 11 | class OrganizationFeature(BaseResqpy): 12 | """Class for generic RESQML Organization Feature objects.""" 13 | 14 | resqml_type = "OrganizationFeature" 15 | valid_kinds = ['earth model', 'fluid', 'stratigraphic', 'structural'] 16 | feature_name = ou.alias_for_attribute("title") 17 | 18 | def __init__(self, 19 | parent_model, 20 | uuid = None, 21 | feature_name = None, 22 | organization_kind = None, 23 | originator = None, 24 | extra_metadata = None): 25 | """Initialises an organization feature object.""" 26 | 27 | self.organization_kind = organization_kind 28 | super().__init__(model = parent_model, 29 | uuid = uuid, 30 | title = feature_name, 31 | originator = originator, 32 | extra_metadata = extra_metadata) 33 | 34 | def is_equivalent(self, other, check_extra_metadata = True): 35 | """Returns True if this feature is essentially the same as the other; otherwise False.""" 36 | 37 | if not isinstance(other, OrganizationFeature): 38 | return False 39 | if self is other or bu.matching_uuids(self.uuid, other.uuid): 40 | return True 41 | return (self.feature_name == other.feature_name and self.organization_kind == other.organization_kind and 42 | ((not check_extra_metadata) or ou.equivalent_extra_metadata(self, other))) 43 | 44 | def _load_from_xml(self): 45 | self.organization_kind = rqet.find_tag_text(self.root, 'OrganizationKind') 46 | 47 | def create_xml(self, add_as_part = True, originator = None, reuse = True): 48 | """Creates an organization feature xml node from this organization feature object.""" 49 | 50 | if reuse and self.try_reuse(): 51 | return self.root # check for reusable (equivalent) object 52 | # create node with citation block 53 | ofn = super().create_xml(add_as_part = False, originator = originator) 54 | 55 | # Extra element for organization_kind 56 | if self.organization_kind not in self.valid_kinds: 57 | raise ValueError(self.organization_kind) 58 | kind_node = rqet.SubElement(ofn, ns['resqml2'] + 'OrganizationKind') 59 | kind_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'OrganizationKind') 60 | kind_node.text = self.organization_kind 61 | 62 | if add_as_part: 63 | self.model.add_part('obj_OrganizationFeature', self.uuid, ofn) 64 | 65 | return ofn 66 | -------------------------------------------------------------------------------- /resqpy/organize/structural_organization_interpretation.py: -------------------------------------------------------------------------------- 1 | """Class for RESQML Structural Organization Interpretation organizational objects.""" 2 | 3 | from resqpy.olio.base import BaseResqpy 4 | 5 | valid_ordering_criteria = ('age', 'apparent depth', 'measured depth') 6 | 7 | valid_contact_relationships = ("frontier feature to frontier feature", "genetic boundary to frontier feature", 8 | "genetic boundary to genetic boundary", "genetic boundary to tectonic boundary", 9 | "stratigraphic unit to frontier feature", "stratigraphic unit to stratigraphic unit", 10 | "tectonic boundary to frontier feature", "tectonic boundary to genetic boundary", 11 | "tectonic boundary to tectonic boundary") 12 | 13 | 14 | class StructuralOrganizationInterpretation(BaseResqpy): 15 | """Class for RESQML Structural Organization Interpretation organizational objects.""" 16 | 17 | resqml_type = 'StructuralOrganizationInterpretation' 18 | 19 | def __init__(self): 20 | """Initialise a structural organisation interpretation.""" 21 | self.ordering_criterion = 'age' #: one of 'age' (youngest to oldest!), 'apparent depth', or 'measured depth' 22 | self.fault_uuid_list = None #: list of uuids of fault interpretation which intersect the structural object 23 | self.horizon_tuple_list = None #: list of horizon interpretation uuids along with index and rank 24 | self.sides_uuid_list = None #: list of uuids of interpretation objects for sides of structural object 25 | self.top_frontier_uuid_list = None #: list of uuids of interpretation objects for top of structural object 26 | self.bottom_frontier_uuid_list = None #: list of uuids of interpretation objects for base of structural object 27 | self.contact_interpretations = None #: list of contact interpretations 28 | 29 | # TODO 30 | -------------------------------------------------------------------------------- /resqpy/organize/tectonic_boundary_feature.py: -------------------------------------------------------------------------------- 1 | """Class for RESQML Tectonic Boundary Feature (fault) organizational objects.""" 2 | 3 | import resqpy.olio.uuid as bu 4 | import resqpy.olio.xml_et as rqet 5 | import resqpy.organize 6 | import resqpy.organize._utils as ou 7 | from resqpy.olio.base import BaseResqpy 8 | from resqpy.olio.xml_namespaces import curly_namespace as ns 9 | 10 | 11 | class TectonicBoundaryFeature(BaseResqpy): 12 | """Class for RESQML Tectonic Boundary Feature (fault) organizational objects.""" 13 | 14 | resqml_type = "TectonicBoundaryFeature" 15 | feature_name = ou.alias_for_attribute("title") 16 | valid_kinds = ('fault', 'fracture') 17 | 18 | def __init__(self, 19 | parent_model, 20 | uuid = None, 21 | kind = None, 22 | feature_name = None, 23 | originator = None, 24 | extra_metadata = None): 25 | """Initialises a tectonic boundary feature (fault or fracture) organisational object.""" 26 | self.kind = kind 27 | super().__init__(model = parent_model, 28 | uuid = uuid, 29 | title = feature_name, 30 | originator = originator, 31 | extra_metadata = extra_metadata) 32 | 33 | def _load_from_xml(self): 34 | self.kind = rqet.find_tag_text(self.root, 'TectonicBoundaryKind') 35 | if not self.kind: 36 | self.kind = 'fault' 37 | 38 | def is_equivalent(self, other, check_extra_metadata = True): 39 | """Returns True if this feature is essentially the same as the other; otherwise False.""" 40 | if other is None or not isinstance(other, TectonicBoundaryFeature): 41 | return False 42 | if self is other or bu.matching_uuids(self.uuid, other.uuid): 43 | return True 44 | if check_extra_metadata and not ou.equivalent_extra_metadata(self, other): 45 | return False 46 | return self.feature_name == other.feature_name and self.kind == other.kind 47 | 48 | def create_xml(self, add_as_part = True, originator = None, reuse = True): 49 | """Creates a tectonic boundary feature organisational xml node from this tectonic boundary feature object.""" 50 | 51 | if reuse and self.try_reuse(): 52 | return self.root # check for reusable (equivalent) object 53 | # create node with citation block 54 | tbf = super().create_xml(add_as_part = False, originator = originator) 55 | 56 | assert self.kind in self.valid_kinds 57 | kind_node = rqet.SubElement(tbf, ns['resqml2'] + 'TectonicBoundaryKind') 58 | kind_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'TectonicBoundaryKind') 59 | kind_node.text = self.kind 60 | 61 | if add_as_part: 62 | self.model.add_part('obj_TectonicBoundaryFeature', self.uuid, tbf) 63 | 64 | return tbf 65 | -------------------------------------------------------------------------------- /resqpy/organize/wellbore_feature.py: -------------------------------------------------------------------------------- 1 | """Class for RESQML Wellbore Feature organizational objects.""" 2 | 3 | import resqpy.olio.uuid as bu 4 | import resqpy.olio.xml_et as rqet 5 | import resqpy.organize 6 | import resqpy.organize._utils as ou 7 | from resqpy.olio.base import BaseResqpy 8 | from resqpy.olio.xml_namespaces import curly_namespace as ns 9 | 10 | 11 | class WellboreFeature(BaseResqpy): 12 | """Class for RESQML Wellbore Feature organizational objects.""" 13 | 14 | # note: optional WITSML link not supported 15 | 16 | resqml_type = "WellboreFeature" 17 | feature_name = ou.alias_for_attribute("title") 18 | 19 | def __init__(self, parent_model, uuid = None, feature_name = None, originator = None, extra_metadata = None): 20 | """Initialises a wellbore feature organisational object.""" 21 | super().__init__(model = parent_model, 22 | uuid = uuid, 23 | title = feature_name, 24 | originator = originator, 25 | extra_metadata = extra_metadata) 26 | 27 | def is_equivalent(self, other, check_extra_metadata = True): 28 | """Returns True if this feature is essentially the same as the other; otherwise False.""" 29 | if other is None or not isinstance(other, WellboreFeature): 30 | return False 31 | if self is other or bu.matching_uuids(self.uuid, other.uuid): 32 | return True 33 | if check_extra_metadata and not ou.equivalent_extra_metadata(self, other): 34 | return False 35 | return self.feature_name == other.feature_name 36 | 37 | def create_xml(self, add_as_part = True, originator = None, reuse = True): 38 | """Creates a wellbore feature organisational xml node from this wellbore feature object.""" 39 | if reuse and self.try_reuse(): 40 | return self.root # check for reusable (equivalent) object 41 | return super().create_xml(add_as_part = add_as_part, originator = originator) 42 | -------------------------------------------------------------------------------- /resqpy/property/__init__.py: -------------------------------------------------------------------------------- 1 | """Collections of properties for grids, wellbore frames, grid connection sets etc.""" 2 | 3 | __all__ = [ 4 | 'PropertyCollection', 'Property', 'AttributePropertySet', 'ApsProperty', 'WellLog', 'WellIntervalProperty', 5 | 'WellIntervalPropertyCollection', 'WellLogCollection', 'StringLookup', 'PropertyKind', 'GridPropertyCollection', 6 | 'property_over_time_series_from_collection', 'property_collection_for_keyword', 'infer_property_kind', 7 | 'write_hdf5_and_create_xml_for_active_property', 'reformat_column_edges_to_resqml_format', 8 | 'reformat_column_edges_from_resqml_format', 'same_property_kind', 'selective_version_of_collection', 9 | 'supported_local_property_kind_list', 'supported_property_kind_list', 'supported_facet_type_list', 10 | 'expected_facet_type_dict', 'create_transmisibility_multiplier_property_kind', 11 | 'property_kind_and_facet_from_keyword', 'guess_uom', 'property_parts', 'property_part', 'make_aps_key', 12 | 'establish_zone_property_kind' 13 | ] 14 | 15 | from .property_common import property_collection_for_keyword, \ 16 | property_over_time_series_from_collection, \ 17 | write_hdf5_and_create_xml_for_active_property, \ 18 | infer_property_kind, \ 19 | reformat_column_edges_to_resqml_format, \ 20 | reformat_column_edges_from_resqml_format, \ 21 | selective_version_of_collection, \ 22 | same_property_kind, \ 23 | supported_property_kind_list, \ 24 | supported_local_property_kind_list, \ 25 | supported_facet_type_list, \ 26 | expected_facet_type_dict, \ 27 | property_kind_and_facet_from_keyword, \ 28 | guess_uom, \ 29 | property_parts, \ 30 | property_part 31 | from .property_kind import PropertyKind, create_transmisibility_multiplier_property_kind, establish_zone_property_kind 32 | from .string_lookup import StringLookup 33 | from .property_collection import PropertyCollection 34 | from .attribute_property_set import AttributePropertySet, ApsProperty, make_aps_key 35 | from .grid_property_collection import GridPropertyCollection 36 | from ._property import Property 37 | from .well_interval_property import WellIntervalProperty 38 | from .well_interval_property_collection import WellIntervalPropertyCollection 39 | from .well_log import WellLog 40 | from .well_log_collection import WellLogCollection 41 | 42 | # Set "module" attribute of all public objects to this path. 43 | for _name in __all__: 44 | _obj = eval(_name) 45 | if hasattr(_obj, "__module__"): 46 | _obj.__module__ = __name__ 47 | -------------------------------------------------------------------------------- /resqpy/property/well_interval_property.py: -------------------------------------------------------------------------------- 1 | """Class for wellintervalproperty, for resqml wellbore frame of blocked wellbore supports.""" 2 | 3 | import logging 4 | 5 | log = logging.getLogger(__name__) 6 | 7 | import resqpy.property 8 | import resqpy.property.property_collection as rqp_pc 9 | 10 | 11 | class WellIntervalProperty: 12 | """Thin wrapper class around interval properties for a Wellbore Frame or Blocked Wellbore. 13 | 14 | ie, interval or cell well logs. 15 | """ 16 | 17 | def __init__(self, collection, part): 18 | """Create an interval log or blocked well log from a part name.""" 19 | 20 | self.collection: rqp_pc.PropertyCollection = collection 21 | self.model = collection.model 22 | self.part = part 23 | 24 | indexable = self.collection.indexable_for_part(part) 25 | assert indexable in ['cells', 'intervals'], 'expected cells or intervals as indexable element' 26 | 27 | self.name = self.model.citation_title_for_part(part) 28 | self.uom = self.collection.uom_for_part(part) 29 | 30 | def values(self): 31 | """Return interval log or blocked well log as numpy array.""" 32 | 33 | return self.collection.cached_part_array_ref(self.part) 34 | -------------------------------------------------------------------------------- /resqpy/property/well_interval_property_collection.py: -------------------------------------------------------------------------------- 1 | """Class for a collection of well interval properties""" 2 | 3 | # NB. This class does not appear to be well formed and is a candidate for deprecation 4 | 5 | import logging 6 | 7 | log = logging.getLogger(__name__) 8 | 9 | import pandas as pd 10 | 11 | import resqpy.property 12 | import resqpy.property.property_collection as rqp_pc 13 | import resqpy.property.well_interval_property as rqp_wip 14 | import resqpy.property.property_common as rqp_c 15 | 16 | 17 | class WellIntervalPropertyCollection(rqp_pc.PropertyCollection): 18 | """Class for RESQML property collection for a WellboreFrame for interval or blocked well logs""" 19 | 20 | def __init__(self, frame = None, property_set_root = None, realization = None): 21 | """Creates a new property collection related to interval or blocked well logs and a wellbore frame.""" 22 | 23 | super().__init__(support = frame, property_set_root = property_set_root, realization = realization) 24 | 25 | def logs(self): 26 | """Generator that yields component Interval log or Blocked well log objects.""" 27 | 28 | return (rqp_wip.WellIntervalProperty(collection = self, part = part) for part in self.parts()) 29 | 30 | def to_pandas(self, include_units = False): 31 | """Returns a dataframe with a column for each well log included in the collection.""" 32 | 33 | cell_indices = [ 34 | rqp_c.return_cell_indices(i, self.support.cell_indices) for i in self.support.cell_grid_link if i != -1 35 | ] 36 | data = {} 37 | for log in self.logs(): 38 | col_name = log.name 39 | values = log.values() 40 | data[col_name] = values 41 | df = pd.DataFrame(data = data, index = cell_indices) 42 | return df 43 | -------------------------------------------------------------------------------- /resqpy/property/well_log.py: -------------------------------------------------------------------------------- 1 | """Class for a welllog, representing resqml properties for well logs""" 2 | 3 | import logging 4 | 5 | log = logging.getLogger(__name__) 6 | 7 | import resqpy.property 8 | import resqpy.property.property_collection as rqp_pc 9 | 10 | 11 | class WellLog: 12 | """Thin wrapper class around RESQML properties for well logs.""" 13 | 14 | def __init__(self, collection, uuid): 15 | """Create a well log from a part name.""" 16 | 17 | self.collection: rqp_pc.PropertyCollection = collection 18 | self.model = collection.model 19 | self.uuid = uuid 20 | 21 | part = self.model.part_for_uuid(uuid) 22 | indexable = self.collection.indexable_for_part(part) 23 | if indexable != 'nodes': 24 | raise NotImplementedError('well frame related property does not have nodes as indexable element') 25 | 26 | #: Name of log 27 | self.title = self.model.citation_title_for_part(part) 28 | 29 | #: Unit of measure 30 | self.uom = self.collection.uom_for_part(part) 31 | 32 | def values(self): 33 | """Return log data as numpy array. 34 | 35 | Note: 36 | may return 2D numpy array with shape (num_depths, num_columns). 37 | """ 38 | 39 | part = self.model.part_for_uuid(self.uuid) 40 | return self.collection.cached_part_array_ref(part) 41 | -------------------------------------------------------------------------------- /resqpy/rq_import/__init__.py: -------------------------------------------------------------------------------- 1 | """Miscellaneous functions for importing from other formats.""" 2 | 3 | # Nexus is a trademark of Halliburton 4 | 5 | __all__ = [ 6 | 'import_nexus', 'import_vdb_all_grids', 'grid_from_cp', 'import_vdb_ensemble', 'add_ab_properties', 'add_surfaces' 7 | ] 8 | 9 | from ._grid_from_cp import grid_from_cp 10 | from ._import_nexus import import_nexus 11 | from ._import_vdb_all_grids import import_vdb_all_grids 12 | from ._import_vdb_ensemble import import_vdb_ensemble 13 | from ._add_ab_properties import add_ab_properties 14 | from ._add_surfaces import add_surfaces 15 | 16 | # Set "module" attribute of all public objects to this path. Fixes issue #398 17 | for _name in __all__: 18 | _obj = eval(_name) 19 | if hasattr(_obj, "__module__"): 20 | _obj.__module__ = __name__ 21 | -------------------------------------------------------------------------------- /resqpy/strata/__init__.py: -------------------------------------------------------------------------------- 1 | """Stratigraphy related classes and valid values.""" 2 | 3 | __all__ = [ 4 | 'BinaryContactInterpretation', 'GeologicUnitInterpretation', 'StratigraphicColumnRank', 'StratigraphicColumn', 5 | 'StratigraphicUnitFeature', 'StratigraphicUnitInterpretation', 'valid_compositions', 'valid_implacements', 6 | 'valid_domains', 'valid_deposition_modes', 'valid_ordering_criteria', 'valid_contact_relationships', 7 | 'valid_contact_verbs', 'valid_contact_sides', 'valid_contact_modes' 8 | ] 9 | 10 | from ._strata_common import valid_compositions, valid_implacements, valid_domains, valid_deposition_modes, \ 11 | valid_ordering_criteria, valid_contact_relationships, valid_contact_verbs, valid_contact_sides, \ 12 | valid_contact_modes 13 | from ._binary_contact_interpretation import BinaryContactInterpretation 14 | from ._geologic_unit_interpretation import GeologicUnitInterpretation 15 | from ._stratigraphic_column_rank import StratigraphicColumnRank 16 | from ._stratigraphic_column import StratigraphicColumn 17 | from ._stratigraphic_unit_feature import StratigraphicUnitFeature 18 | from ._stratigraphic_unit_interpretation import StratigraphicUnitInterpretation 19 | 20 | # Set "module" attribute of all public objects to this path. Fixes issue #310 21 | for _name in __all__: 22 | _obj = eval(_name) 23 | if hasattr(_obj, "__module__"): 24 | _obj.__module__ = __name__ 25 | -------------------------------------------------------------------------------- /resqpy/strata/_strata_common.py: -------------------------------------------------------------------------------- 1 | """Common functions and valid xml constant values for stratigraphy related RESQML classes.""" 2 | 3 | # note: two compositions have a spurious trailing space in the RESQML xsd; resqpy hides this from calling code 4 | valid_compositions = [ 5 | 'intrusive clay ', 'intrusive clay', 'organic', 'intrusive mud ', 'intrusive mud', 'evaporite salt', 6 | 'evaporite non salt', 'sedimentary siliclastic', 'carbonate', 'magmatic intrusive granitoid', 7 | 'magmatic intrusive pyroclastic', 'magmatic extrusive lava flow', 'other chemichal rock', 'sedimentary turbidite' 8 | ] 9 | 10 | valid_implacements = ['autochtonous', 'allochtonous'] 11 | 12 | valid_domains = ('depth', 'time', 'mixed') 13 | 14 | valid_deposition_modes = [ 15 | 'proportional between top and bottom', 'parallel to bottom', 'parallel to top', 'parallel to another boundary' 16 | ] 17 | 18 | valid_ordering_criteria = ['age', 'apparent depth', 'measured depth'] # stratigraphic column must be ordered by age 19 | 20 | valid_contact_relationships = [ 21 | 'frontier feature to frontier feature', 'genetic boundary to frontier feature', 22 | 'genetic boundary to genetic boundary', 'genetic boundary to tectonic boundary', 23 | 'stratigraphic unit to frontier feature', 'stratigraphic unit to stratigraphic unit', 24 | 'tectonic boundary to frontier feature', 'tectonic boundary to genetic boundary', 25 | 'tectonic boundary to tectonic boundary' 26 | ] 27 | 28 | valid_contact_verbs = ['splits', 'interrupts', 'contains', 'erodes', 'stops at', 'crosses', 'includes'] 29 | 30 | valid_contact_sides = ['footwall', 'hanging wall', 'north', 'south', 'east', 'west', 'younger', 'older', 'both'] 31 | 32 | valid_contact_modes = ['baselap', 'erosion', 'extended', 'proportional'] 33 | 34 | 35 | def _index_attr(obj): 36 | """Returns the index attribute of any object – typically used as a sort key function.""" 37 | return obj.index 38 | -------------------------------------------------------------------------------- /resqpy/surface/__init__.py: -------------------------------------------------------------------------------- 1 | """Classes for RESQML objects related to surfaces.""" 2 | 3 | __all__ = [ 4 | 'BaseSurface', 'CombinedSurface', 'Mesh', 'TriangulatedPatch', 'PointSet', 'Surface', 'TriMesh', 'TriMeshStencil', 5 | 'distill_triangle_points', '_adjust_flange_z' 6 | ] 7 | 8 | from ._base_surface import BaseSurface 9 | from ._combined_surface import CombinedSurface 10 | from ._mesh import Mesh 11 | from ._triangulated_patch import TriangulatedPatch 12 | from ._pointset import PointSet 13 | from ._surface import Surface, distill_triangle_points, nan_removed_triangles_and_points, _adjust_flange_z 14 | from ._tri_mesh import TriMesh 15 | from ._tri_mesh_stencil import TriMeshStencil 16 | 17 | # Set "module" attribute of all public objects to this path. 18 | for _name in __all__: 19 | _obj = eval(_name) 20 | if hasattr(_obj, "__module__"): 21 | _obj.__module__ = __name__ 22 | -------------------------------------------------------------------------------- /resqpy/surface/_base_surface.py: -------------------------------------------------------------------------------- 1 | """_base_surface.py: base_surface class based on resqml standard.""" 2 | 3 | import logging 4 | 5 | log = logging.getLogger(__name__) 6 | 7 | import resqpy.organize as rqo 8 | from resqpy.olio.base import BaseResqpy 9 | 10 | 11 | class BaseSurface(BaseResqpy): 12 | """Base class to implement shared methods for other classes in this module.""" 13 | 14 | def create_interpretation_and_feature(self, 15 | kind = 'horizon', 16 | name = None, 17 | interp_title_suffix = None, 18 | is_normal = True): 19 | """Creates xml and objects for a represented interpretaion and interpreted feature, if not already present.""" 20 | 21 | assert kind in ['horizon', 'fault', 'fracture', 'geobody boundary'] 22 | assert name or self.title, 'title missing' 23 | if not name: 24 | name = self.title 25 | 26 | if self.represented_interpretation_root is not None: 27 | log.debug(f'represented interpretation already exisrs for surface {self.title}') 28 | return 29 | if kind in ['horizon', 'geobody boundary']: 30 | feature = rqo.GeneticBoundaryFeature(self.model, kind = kind, feature_name = name) 31 | feature.create_xml() 32 | if kind == 'horizon': 33 | interp = rqo.HorizonInterpretation(self.model, genetic_boundary_feature = feature, domain = 'depth') 34 | else: 35 | interp = rqo.GeobodyBoundaryInterpretation(self.model, 36 | genetic_boundary_feature = feature, 37 | domain = 'depth') 38 | elif kind in ['fault', 'fracture']: 39 | feature = rqo.TectonicBoundaryFeature(self.model, kind = kind, feature_name = name) 40 | feature.create_xml() 41 | interp = rqo.FaultInterpretation(self.model, 42 | is_normal = is_normal, 43 | tectonic_boundary_feature = feature, 44 | domain = 'depth') # might need more arguments 45 | else: 46 | log.critical('code failure') 47 | interp_root = interp.create_xml(title_suffix = interp_title_suffix) 48 | self.set_represented_interpretation_root(interp_root) 49 | -------------------------------------------------------------------------------- /resqpy/surface/_combined_surface.py: -------------------------------------------------------------------------------- 1 | """Combined surface class.""" 2 | 3 | import logging 4 | 5 | log = logging.getLogger(__name__) 6 | 7 | import numpy as np 8 | 9 | 10 | class CombinedSurface: 11 | """Class allowing a collection of Surface objects to be treated as a single surface. 12 | 13 | Not a RESQML class in its own right. 14 | """ 15 | 16 | def __init__(self, surface_list, crs_uuid = None): 17 | """Initialise a CombinedSurface object from a list of Surface (and/or CombinedSurface) objects. 18 | 19 | arguments: 20 | surface_list (list of Surface and/or CombinedSurface objects): the new object is the combination of these surfaces 21 | crs_uuid (uuid.UUID, optional): if present, all contributing surfaces must refer to this crs 22 | 23 | note: 24 | all contributing surfaces should be established before initialising this object; 25 | all contributing surfaces must refer to the same crs; this class of object is not part of the RESQML 26 | standard and cannot be saved in a RESQML dataset - it is a high level derived object class 27 | """ 28 | 29 | assert len(surface_list) > 0 30 | self.surface_list = surface_list 31 | self.crs_uuid = crs_uuid 32 | if self.crs_uuid is None: 33 | self.crs_uuid = surface_list[0].crs_uuid 34 | self.patch_count_list = [] 35 | self.triangle_count_list = [] 36 | self.points_count_list = [] 37 | self.is_combined_list = [] 38 | self.triangles = None 39 | self.points = None 40 | for surface in surface_list: 41 | is_combined = isinstance(surface, CombinedSurface) 42 | self.is_combined_list.append(is_combined) 43 | if is_combined: 44 | self.patch_count_list.append(sum(surface.patch_count_list)) 45 | else: 46 | self.patch_count_list.append(len(surface.patch_list)) 47 | t, p = surface.triangles_and_points() 48 | self.triangle_count_list.append(len(t)) 49 | self.points_count_list.append(len(p)) 50 | 51 | def surface_index_for_triangle_index(self, tri_index): 52 | """Return the index of the surface containing the triangle and local triangle index. 53 | 54 | Arguments: 55 | tri_index: triangle index in the combined surface 56 | """ 57 | for s_i in range(len(self.surface_list)): 58 | if tri_index < self.triangle_count_list[s_i]: 59 | return s_i, tri_index 60 | tri_index -= self.triangle_count_list[s_i] 61 | return None 62 | 63 | def triangles_and_points(self): 64 | """Returns the composite triangles and points for the combined surface.""" 65 | 66 | if self.triangles is None: 67 | points_offset = 0 68 | for surface in self.surface_list: 69 | (t, p) = surface.triangles_and_points() 70 | if points_offset == 0: 71 | self.triangles = t.copy() 72 | self.points = p.copy() 73 | else: 74 | self.triangles = np.concatenate((self.triangles, t.copy() + points_offset)) 75 | self.points = np.concatenate((self.points, p.copy())) 76 | points_offset += p.shape[0] 77 | 78 | return self.triangles, self.points 79 | -------------------------------------------------------------------------------- /resqpy/time_series/__init__.py: -------------------------------------------------------------------------------- 1 | """Time series classes and functions.""" 2 | 3 | __all__ = [ 4 | 'TimeSeries', 'GeologicTimeSeries', 'AnyTimeSeries', 'TimeDuration', 'selected_time_series', 'simplified_timestamp', 5 | 'cleaned_timestamp', 'time_series_from_list', 'merge_timeseries_from_uuid', 'geologic_time_str', 6 | 'timeframe_for_time_series_uuid', 'any_time_series', 'time_series_from_nexus_summary', 'check_timestamp', 7 | 'colloquial_date' 8 | ] 9 | 10 | from ._any_time_series import AnyTimeSeries 11 | from ._geologic_time_series import GeologicTimeSeries 12 | from ._time_duration import TimeDuration 13 | from ._time_series import TimeSeries, check_timestamp, colloquial_date 14 | from ._functions import selected_time_series, simplified_timestamp, cleaned_timestamp, time_series_from_list, \ 15 | merge_timeseries_from_uuid, geologic_time_str, timeframe_for_time_series_uuid, any_time_series 16 | from ._from_nexus_summary import time_series_from_nexus_summary 17 | 18 | # Set "module" attribute of all public objects to this path. 19 | for _name in __all__: 20 | _obj = eval(_name) 21 | if hasattr(_obj, "__module__"): 22 | _obj.__module__ = __name__ 23 | -------------------------------------------------------------------------------- /resqpy/time_series/_geologic_time_series.py: -------------------------------------------------------------------------------- 1 | """Geologic time series.""" 2 | 3 | import logging 4 | 5 | log = logging.getLogger(__name__) 6 | 7 | import resqpy.time_series 8 | import resqpy.time_series._any_time_series as ats 9 | 10 | 11 | class GeologicTimeSeries(ats.AnyTimeSeries): 12 | """Class for RESQML Time Series using only year offsets (for geological time frames).""" 13 | 14 | def __init__(self, parent_model, uuid = None, title = None, originator = None, extra_metadata = None): 15 | """Create a GeologicTimeSeries object, either from a time series node in parent model, or empty. 16 | 17 | arguments: 18 | parent_model (model.Model): the resqpy model to which the time series will belong 19 | uuid (uuid.UUID, optional): the uuid of a TimeSeries object to be loaded from xml 20 | title (str, optional): the citation title to use for a new time series; 21 | ignored if uuid is not None 22 | originator (str, optional): the name of the person creating the time series, defaults to login id; 23 | ignored if uuid is not None 24 | extra_metadata (dict, optional): string key, value pairs to add as extra metadata for the time series; 25 | ignored if uuid is not None 26 | 27 | returns: 28 | newly instantiated GeologicTimeSeries object 29 | 30 | note: 31 | if instantiating from an existing RESQML time series, its Time entries must all have YearOffset data 32 | which should be large negative integers (or zero if reaching the current era) 33 | 34 | :meta common: 35 | """ 36 | self.timeframe = 'geologic' 37 | self.timestamps = [] # ordered list of (large negative) ints being year offsets from present 38 | super().__init__(model = parent_model, 39 | uuid = uuid, 40 | title = title, 41 | originator = originator, 42 | extra_metadata = extra_metadata) 43 | if self.extra_metadata is not None and self.extra_metadata.get('timeframe') == 'human': 44 | raise ValueError('attempt to instantiate a geologic time series for a human timeframe time series') 45 | 46 | @classmethod 47 | def from_year_list(cls, parent_model, year_list, title = None, originator = None, extra_metadata = {}): 48 | """Creates a new GeologicTimeSeries from a list of large integers representing years before present. 49 | 50 | note: 51 | the years will be converted to negative numbers if positive, and sorted from oldest (most negative) 52 | to youngest (least negative) 53 | 54 | :meta common: 55 | """ 56 | 57 | assert isinstance(year_list, list) and len(year_list) > 0 58 | negative_list = [] 59 | for year in year_list: 60 | assert isinstance(year, int) 61 | if year > 0: 62 | negative_list.append(-year) 63 | else: 64 | negative_list.append(year) 65 | 66 | gts = cls(parent_model, title = title, originator = originator, extra_metadata = extra_metadata) 67 | 68 | gts.timestamps = sorted(negative_list) 69 | 70 | return gts 71 | 72 | def is_equivalent(self, other_ts): 73 | """Returns True if the this geologic time series is essentially identical to the other; otherwise False.""" 74 | 75 | super_equivalence = super().is_equivalent(other_ts) 76 | if super_equivalence is not None: 77 | return super_equivalence 78 | return self.timestamps == other_ts.timestamps # has no tolerance of small differences 79 | -------------------------------------------------------------------------------- /resqpy/time_series/_time_duration.py: -------------------------------------------------------------------------------- 1 | """Time duration""" 2 | 3 | import logging 4 | 5 | log = logging.getLogger(__name__) 6 | 7 | import datetime as dt 8 | import resqpy.time_series as rqts 9 | 10 | 11 | class TimeDuration: 12 | """A thin wrapper around python's datetime timedelta objects (not a RESQML class).""" 13 | 14 | def __init__(self, 15 | days = None, 16 | hours = None, 17 | minutes = None, 18 | seconds = None, 19 | earlier_timestamp = None, 20 | later_timestamp = None): 21 | """Create a TimeDuration object either from days and seconds or from a pair of timestamps.""" 22 | 23 | # for negative durations, earlier_timestamp should be later than later_timestamp 24 | # or days, hours etc. should typically all be non-positive 25 | # ie. days = -1, hours = -12 will be a negative one and a half day duration 26 | # whilst days = -1, hours = 12 will be a negative half day duration 27 | self.duration = None 28 | if earlier_timestamp is not None and later_timestamp is not None: 29 | rqts.check_timestamp(earlier_timestamp) 30 | rqts.check_timestamp(later_timestamp) 31 | if earlier_timestamp.endswith('Z'): 32 | earlier_timestamp = earlier_timestamp[:-1] # Trailing Z is not part of iso format 33 | if later_timestamp.endswith('Z'): 34 | later_timestamp = later_timestamp[:-1] 35 | dt_earlier = dt.datetime.fromisoformat(earlier_timestamp) 36 | dt_later = dt.datetime.fromisoformat(later_timestamp) 37 | self.duration = dt_later - dt_earlier 38 | else: 39 | if days is None: 40 | days = 0 41 | if hours is None: 42 | hours = 0 43 | if minutes is None: 44 | minutes = 0 45 | if seconds is None: 46 | seconds = 0 47 | self.duration = dt.timedelta(days = days, hours = hours, minutes = minutes, seconds = seconds) 48 | 49 | def timestamp_after_duration(self, earlier_timestamp): 50 | """Create a new timestamp from this duration and an earlier timestamp.""" 51 | 52 | if earlier_timestamp.endswith('Z'): 53 | earlier_timestamp = earlier_timestamp[:-1] 54 | rqts.check_timestamp(earlier_timestamp) 55 | dt_earlier = dt.datetime.fromisoformat(earlier_timestamp) 56 | dt_result = dt_earlier + self.duration 57 | return dt_result.isoformat() + 'Z' 58 | 59 | def timestamp_before_duration(self, later_timestamp): 60 | """Create a new timestamp from this duration and a later timestamp.""" 61 | 62 | if later_timestamp.endswith('Z'): 63 | later_timestamp = later_timestamp[:-1] 64 | rqts.check_timestamp(later_timestamp) 65 | dt_later = dt.datetime.fromisoformat(later_timestamp) 66 | dt_result = dt_later - self.duration 67 | return dt_result.isoformat() + 'Z' 68 | -------------------------------------------------------------------------------- /resqpy/unstructured/__init__.py: -------------------------------------------------------------------------------- 1 | """Unstructured grid and derived classes.""" 2 | 3 | __all__ = [ 4 | 'UnstructuredGrid', 'HexaGrid', 'PrismGrid', 'VerticalPrismGrid', 'PyramidGrid', 'TetraGrid', 'valid_cell_shapes' 5 | ] 6 | 7 | from ._unstructured_grid import UnstructuredGrid, valid_cell_shapes 8 | from ._hexa_grid import HexaGrid 9 | from ._prism_grid import PrismGrid, VerticalPrismGrid 10 | from ._pyramid_grid import PyramidGrid 11 | from ._tetra_grid import TetraGrid 12 | 13 | # Set "module" attribute of all public objects to this path. Fixes issue #310 14 | for _name in __all__: 15 | _obj = eval(_name) 16 | if hasattr(_obj, "__module__"): 17 | _obj.__module__ = __name__ 18 | -------------------------------------------------------------------------------- /resqpy/weights_and_measures/__init__.py: -------------------------------------------------------------------------------- 1 | """Weights and measures valid units and unit conversion functions.""" 2 | 3 | __all__ = [ 4 | 'UOM_ALIASES', 'UOM_ALIAS_MAP', 'CASE_INSENSITIVE_UOMS', 'rq_uom', 'convert', 'valid_uoms', 'valid_quantities', 5 | 'valid_property_kinds', 'nexus_uom_for_quantity', 'rq_uom_list', 'rq_length_unit', 'rq_time_unit', 'convert_times', 6 | 'convert_lengths', 'convert_pressures', 'convert_volumes', 'convert_flow_rates', 'convert_transmissibilities', 7 | 'get_conversion_factors' 8 | ] 9 | 10 | from .weights_and_measures import (UOM_ALIASES, UOM_ALIAS_MAP, CASE_INSENSITIVE_UOMS, rq_uom, convert, valid_uoms, 11 | valid_quantities, valid_property_kinds, rq_uom_list, rq_length_unit, rq_time_unit, 12 | convert_times, convert_lengths, convert_pressures, convert_volumes, 13 | convert_flow_rates, convert_transmissibilities, get_conversion_factors) 14 | from .nexus_units import nexus_uom_for_quantity 15 | 16 | # Set "module" attribute of all public objects to this path. 17 | for _name in __all__: 18 | _obj = eval(_name) 19 | if hasattr(_obj, "__module__"): 20 | _obj.__module__ = __name__ 21 | -------------------------------------------------------------------------------- /resqpy/well/__init__.py: -------------------------------------------------------------------------------- 1 | """Classes relating to wells.""" 2 | 3 | __all__ = [ 4 | 'MdDatum', 'BlockedWell', 'Trajectory', 'DeviationSurvey', 'WellboreFrame', 'WellboreMarkerFrame', 'WellboreMarker', 5 | 'add_wells_from_ascii_file', 'well_name', 'add_las_to_trajectory', 'add_blocked_wells_from_wellspec', 6 | 'add_logs_from_cellio', 'lookup_from_cellio' 7 | ] 8 | 9 | from ._md_datum import MdDatum 10 | from ._wellbore_marker import WellboreMarker 11 | from ._wellbore_marker_frame import WellboreMarkerFrame 12 | from ._blocked_well import BlockedWell 13 | from ._trajectory import Trajectory 14 | from ._deviation_survey import DeviationSurvey 15 | from ._wellbore_frame import WellboreFrame 16 | 17 | from .well_object_funcs import add_wells_from_ascii_file, well_name, add_las_to_trajectory, \ 18 | add_blocked_wells_from_wellspec, add_logs_from_cellio, lookup_from_cellio 19 | 20 | # Set "module" attribute of all public objects to this path. Fixes issue #310 21 | for _name in __all__: 22 | _obj = eval(_name) 23 | if hasattr(_obj, "__module__"): 24 | _obj.__module__ = __name__ 25 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | # The following code allows custom flags when running pytest. In the future we could add --integrationtest and --unittest etc. 5 | def pytest_addoption(parser): 6 | parser.addoption("--buildtest", action = "store_true", default = False, help = "run build tests") 7 | 8 | 9 | def pytest_configure(config): 10 | config.addinivalue_line("markers", "buildtest: add --buildtest to run") 11 | 12 | 13 | def pytest_collection_modifyitems(config, items): 14 | if config.getoption("--buildtest"): 15 | # --buildtest given in cli: do not skip build tests 16 | return 17 | skip_slow = pytest.mark.skip(reason = "need --buildtest option to run") 18 | for item in items: 19 | if "buildtest" in item.keywords: 20 | item.add_marker(skip_slow) 21 | -------------------------------------------------------------------------------- /tests/integration_tests/conftest.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from shutil import copytree 3 | 4 | import pytest 5 | 6 | 7 | @pytest.fixture 8 | def test_data_path(tmp_path): 9 | """ Return pathlib.Path pointing to temporary copy of tests/example_data 10 | 11 | Use a fresh temporary directory for each test. 12 | """ 13 | master_path = (Path(__file__) / '../../test_data').resolve() 14 | data_path = Path(tmp_path) / 'test_data' 15 | 16 | assert master_path.exists() 17 | assert not data_path.exists() 18 | copytree(str(master_path), str(data_path)) 19 | return data_path 20 | -------------------------------------------------------------------------------- /tests/integration_tests/test_grid_transmissibility_snapshots.py: -------------------------------------------------------------------------------- 1 | import os 2 | from inspect import getsourcefile 3 | import numpy as np 4 | 5 | import resqpy.model as rq 6 | 7 | 8 | def write_snapshot(data, title): 9 | # Method to write out a new snapshot of data if one is needed 10 | reshaped_array = data.reshape(data.shape[0], -1) 11 | np.savetxt(f'C:\\Test\\{title}.txt', reshaped_array) 12 | 13 | 14 | def check_load_snapshot(data, filename): 15 | # Compare the actual data against the stored expected array 16 | loaded_array = np.loadtxt(filename) 17 | expected_array = loaded_array.reshape(loaded_array.shape[0], loaded_array.shape[1] // data.shape[2], data.shape[2]) 18 | np.testing.assert_array_almost_equal(data, expected_array, decimal = 4) 19 | 20 | 21 | def test_check_transmisibility_output(test_data_path): 22 | current_filename = os.path.split(getsourcefile(lambda: 0))[0] 23 | base_folder = os.path.dirname(os.path.dirname(current_filename)) 24 | 25 | resqml_file_root = base_folder + '/example_data/block.epc' 26 | grid_model = rq.Model(resqml_file_root) 27 | resqml_grid = grid_model.grid() 28 | k, j, i = resqml_grid.transmissibility() 29 | # snapshots were taken with inflated transmissibilities 30 | k *= 100.0 31 | j *= 100.0 32 | i *= 100.0 33 | 34 | snapshot_filename = current_filename + "/snapshots/transmissibility/" 35 | check_load_snapshot(i, f'{snapshot_filename}block_i.txt') 36 | check_load_snapshot(j, f'{snapshot_filename}block_j.txt') 37 | check_load_snapshot(k, f'{snapshot_filename}block_k.txt') 38 | -------------------------------------------------------------------------------- /tests/test_data/Charisma_example.txt: -------------------------------------------------------------------------------- 1 | INLINE- 2147483647 2147483647 1011700.73566 9483470.13910 2007.76680 Example_fault 1 2 | INLINE- 2147483647 2147483647 1011720.07672 9483470.13910 2035.44941 Example_fault 1 3 | INLINE- 2147483647 2147483647 1011794.70331 9483470.13910 2058.89434 Example_fault 1 4 | INLINE- 2147483647 2147483647 1011942.09576 9483470.13910 2092.91504 Example_fault 1 5 | INLINE- 2147483647 2147483647 1011779.50482 9483708.05096 2004.14238 Example_fault 2 6 | INLINE- 2147483647 2147483647 1011801.85919 9483708.05096 2032.38242 Example_fault 2 7 | INLINE- 2147483647 2147483647 1011857.10284 9483708.05096 2051.86504 Example_fault 2 8 | INLINE- 2147483647 2147483647 1011903.90265 9483708.05096 2066.98555 Example_fault 2 9 | INLINE- 2147483647 2147483647 1012048.84564 9483708.05096 2091.06836 Example_fault 2 10 | INLINE- 2147483647 2147483647 1011865.43439 9483945.96259 2004.74238 Example_fault 3 11 | INLINE- 2147483647 2147483647 1011896.81769 9483945.96259 2041.33223 Example_fault 3 12 | INLINE- 2147483647 2147483647 1012053.98383 9483945.96259 2072.00156 Example_fault 3 13 | INLINE- 2147483647 2147483647 1012273.88300 9483945.96259 2093.20020 Example_fault 3 14 | INLINE- 2147483647 2147483647 1011926.62091 9484183.87445 2005.43770 Example_fault 4 15 | INLINE- 2147483647 2147483647 1012063.83282 9484183.87445 2037.90391 Example_fault 4 16 | INLINE- 2147483647 2147483647 1012294.41620 9484183.87445 2068.97813 Example_fault 4 17 | INLINE- 2147483647 2147483647 1012432.41144 9484183.87445 2089.38164 Example_fault 4 18 | INLINE- 2147483647 2147483647 1012634.78589 9484183.87445 2095.04219 Example_fault 4 19 | INLINE- 2147483647 2147483647 1012084.89020 9484421.78607 2003.07598 Example_fault 5 20 | INLINE- 2147483647 2147483647 1012218.65057 9484421.78607 2029.83750 Example_fault 5 21 | INLINE- 2147483647 2147483647 1012362.31000 9484421.78607 2047.20664 Example_fault 5 22 | INLINE- 2147483647 2147483647 1012473.62299 9484421.78607 2067.97930 Example_fault 5 23 | INLINE- 2147483647 2147483647 1012554.38464 9484421.78607 2071.02422 Example_fault 5 24 | -------------------------------------------------------------------------------- /tests/test_data/Charisma_points.txt: -------------------------------------------------------------------------------- 1 | INLINE : 25701 XLINE : 23693 420691.19624 6292314.22044 2799.05591 2 | INLINE : 25701 XLINE : 23694 420680.15765 6292308.35532 2798.08496 3 | INLINE : 25701 XLINE : 23695 420669.11907 6292302.49019 2798.56592 4 | INLINE : 25701 XLINE : 23696 420658.08049 6292296.62507 2799.31055 5 | INLINE : 25701 XLINE : 23697 420647.04190 6292290.75995 2799.00537 6 | INLINE : 25701 XLINE : 23698 420636.00332 6292284.89483 2798.11792 7 | INLINE : 25701 XLINE : 23699 420624.96473 6292279.02970 2797.75220 8 | INLINE : 25701 XLINE : 23700 420613.92615 6292273.16458 2797.47607 9 | INLINE : 25702 XLINE : 22900 429438.92809 6296976.30174 2895.28198 10 | INLINE : 25702 XLINE : 22901 429427.88950 6296970.43661 2895.15771 11 | INLINE : 25702 XLINE : 22902 429416.85092 6296964.57149 2895.23901 12 | INLINE : 25702 XLINE : 22903 429405.81234 6296958.70637 2895.29688 13 | INLINE : 25702 XLINE : 22904 429394.77375 6296952.84124 2895.31128 14 | INLINE : 25702 XLINE : 22905 429383.73517 6296946.97612 2895.40430 15 | INLINE : 25702 XLINE : 22906 429372.69659 6296941.11100 2895.52881 -------------------------------------------------------------------------------- /tests/test_data/IRAP_example.txt: -------------------------------------------------------------------------------- 1 | 1014355.277222 9486584.854492 1999.999609 2 | 1014345.045410 9486053.601654 1999.999609 3 | 1014374.347473 9485115.425232 1999.999609 4 | 1014665.254944 9484601.075623 1999.999609 5 | 1014999.266663 9484305.017639 1999.999609 6 | 1015318.063538 9484507.008850 1999.999609 7 | 1015427.259460 9484870.206482 1999.999609 8 | 1015486.096497 9485676.968765 1999.999609 9 | 1015472.805359 9486499.667725 1999.999609 10 | 1015286.429260 9487363.548157 1999.999609 11 | 1015092.647766 9487635.660583 1999.999609 12 | 1014781.972717 9487661.775208 1999.999609 13 | 1014510.488953 9487519.588806 1999.999609 14 | 1014495.047913 9486967.762268 1999.999609 15 | 1014355.277222 9486584.854492 1999.999609 16 | 999.0 999.0 999.0 17 | 1012785.578106 9485942.950214 -0.000000 18 | 999.0 999.0 999.0 19 | -------------------------------------------------------------------------------- /tests/test_data/IRAP_points.txt: -------------------------------------------------------------------------------- 1 | 429450.658333 6296954.224574 2403.837646 2 | 429444.793211 6296965.263155 2403.449707 3 | 429438.928088 6296976.301737 2403.005615 4 | 429433.062966 6296987.340319 2402.510010 5 | 429427.197843 6296998.378901 2401.972412 6 | 429421.332720 6297009.417483 2401.397949 7 | 429415.467598 6297020.456065 2400.794678 8 | 429409.602475 6297031.494647 2400.166992 9 | 429403.737353 6297042.533228 2399.487793 -------------------------------------------------------------------------------- /tests/test_data/README.md: -------------------------------------------------------------------------------- 1 | ## Example data 2 | 3 | Place example .epc and .h5 files in this directory for use in units tests. 4 | 5 | Ideally files should be less than a megabyte. 6 | -------------------------------------------------------------------------------- /tests/test_data/Surface_roxartext.txt: -------------------------------------------------------------------------------- 1 | -996 5 50.000000 50.000000 2 | 1026651.000000 1026901.000000 9486221.000000 9486421.000000 3 | 6 0.000000 1026651.000000 9486221.000000 4 | 0 0 0 0 0 0 0 5 | 9999900.0000 9999900.0000 9999900.0000 9999900.0000 9999900.0000 9999900.0000 6 | 9999900.0000 0.4229 0.4648 0.4775 0.4609 9999900.0000 7 | 9999900.0000 0.4580 0.5000 0.5127 0.4961 9999900.0000 8 | 9999900.0000 0.4648 0.5068 0.5195 0.5029 9999900.0000 9 | 9999900.0000 9999900.0000 9999900.0000 9999900.0000 9999900.0000 9999900.0000 10 | -------------------------------------------------------------------------------- /tests/test_data/Surface_roxartext_rotate.txt: -------------------------------------------------------------------------------- 1 | -996 5 50.000000 50.000000 2 | 1026651.000000 1026901.000000 9486221.000000 9486421.000000 3 | 6 45.000000 1026651.000000 9486221.000000 4 | 0 0 0 0 0 0 0 5 | 9999900.0000 9999900.0000 9999900.0000 9999900.0000 9999900.0000 9999900.0000 6 | 9999900.0000 0.4229 0.4648 0.4775 0.4609 9999900.0000 7 | 9999900.0000 0.4580 0.5000 0.5127 0.4961 9999900.0000 8 | 9999900.0000 0.4648 0.5068 0.5195 0.5029 9999900.0000 9 | 9999900.0000 9999900.0000 9999900.0000 9999900.0000 9999900.0000 9999900.0000 10 | -------------------------------------------------------------------------------- /tests/test_data/Surface_tsurf.txt: -------------------------------------------------------------------------------- 1 | GOCAD TSurf 1.0 2 | HEADER { 3 | name: E29_Top 4 | } 5 | VRTX 7 1026701.0000000 9486271.0000000 0.4228516 6 | VRTX 8 1026751.0000000 9486271.0000000 0.4648438 7 | VRTX 9 1026801.0000000 9486271.0000000 0.4775391 8 | VRTX 10 1026851.0000000 9486271.0000000 0.4609375 9 | VRTX 11 1026701.0000000 9486321.0000000 0.4580078 10 | VRTX 12 1026751.0000000 9486321.0000000 0.5000000 11 | VRTX 13 1026801.0000000 9486321.0000000 0.5126953 12 | VRTX 14 1026851.0000000 9486321.0000000 0.4960938 13 | VRTX 15 1026701.0000000 9486371.0000000 0.4648438 14 | VRTX 16 1026751.0000000 9486371.0000000 0.5068359 15 | VRTX 17 1026801.0000000 9486371.0000000 0.5195313 16 | VRTX 18 1026851.0000000 9486371.0000000 0.5029297 17 | TRGL 7 8 11 18 | TRGL 8 12 11 19 | TRGL 8 9 12 20 | TRGL 9 13 12 21 | TRGL 9 10 13 22 | TRGL 10 14 13 23 | TRGL 11 12 15 24 | TRGL 12 16 15 25 | TRGL 12 13 16 26 | TRGL 15 17 16 27 | TRGL 15 14 17 28 | TRGL 14 18 17 29 | END 30 | -------------------------------------------------------------------------------- /tests/test_data/Surface_tsurf_2.txt: -------------------------------------------------------------------------------- 1 | GOCAD TSurf 1 2 | HEADER { 3 | name:T_sand 4 | } 5 | GOCAD_ORIGINAL_COORDINATE_SYSTEM 6 | NAME Default 7 | AXIS_NAME "X" "Y" "Z" 8 | AXIS_UNIT "U.S. ft" "U.S. ft" "ft" 9 | ZPOSITIVE Depth 10 | END_ORIGINAL_COORDINATE_SYSTEM 11 | TFACEVRTX 7 1026701.0000000 9486271.0000000 0.4228516 CNXYZ 12 | VRTX 8 1026751.0000000 9486271.0000000 0.4648438 CNXYZ 13 | VRTX 9 1026801.0000000 9486271.0000000 0.4775391 CNXYZ 14 | VRTX 10 1026851.0000000 9486271.0000000 0.4609375 CNXYZ 15 | VRTX 11 1026701.0000000 9486321.0000000 0.4580078 CNXYZ 16 | VRTX 12 1026751.0000000 9486321.0000000 0.5000000 CNXYZ 17 | VRTX 13 1026801.0000000 9486321.0000000 0.5126953 CNXYZ 18 | VRTX 14 1026851.0000000 9486321.0000000 0.4960938 CNXYZ 19 | VRTX 15 1026701.0000000 9486371.0000000 0.4648438 CNXYZ 20 | VRTX 16 1026751.0000000 9486371.0000000 0.5068359 CNXYZ 21 | VRTX 17 1026801.0000000 9486371.0000000 0.5195313 CNXYZ 22 | VRTX 18 1026851.0000000 9486371.0000000 0.5029297 CNXYZ 23 | TRGL 7 8 11 ABC 24 | TRGL 8 12 11 ABC 25 | TRGL 8 9 12 ABC 26 | TRGL 9 13 12 ABC 27 | TRGL 9 10 13 ABC 28 | TRGL 10 14 13 ABC 29 | TRGL 11 12 15 ABC 30 | TRGL 12 16 15 ABC 31 | TRGL 12 13 16 ABC 32 | TRGL 15 17 16 ABC 33 | TRGL 15 14 17 ABC 34 | TRGL 14 18 17 ABC 35 | END 36 | -------------------------------------------------------------------------------- /tests/test_data/Surface_zmap.dat: -------------------------------------------------------------------------------- 1 | ! RMS - Reservoir Modeling System GRID TO ZYCOR GRID 2 | ! RMS - Reservoir Modeling System VERSION : 12.1 3 | ! GRID FILE NAME : Surface_zmap.dat 4 | ! CREATION DATE : 2021.11.09 5 | ! CREATION TIME : 07:17:25 6 | @Surface_zmap.dat HEADER , GRID, 5 7 | 15, -99999.0000, , 4, 1 8 | 5, 6, 1026651.0000, 1026901.0000, 9486221.0000, 9486421.0000 9 | 0.0000, 0.0000, 0.0000 10 | @ 11 | + Grid data starts after this line 12 | -99999.0000 -99999.0000 -99999.0000 -99999.0000 -99999.0000 13 | -99999.0000 0.4648 0.4580 0.4229 -99999.0000 14 | -99999.0000 0.5068 0.5000 0.4648 -99999.0000 15 | -99999.0000 0.5195 0.5127 0.4775 -99999.0000 16 | -99999.0000 0.5029 0.4961 0.4609 -99999.0000 17 | -99999.0000 -99999.0000 -99999.0000 -99999.0000 -99999.0000 18 | -------------------------------------------------------------------------------- /tests/test_data/dateless_timeseries.sum: -------------------------------------------------------------------------------- 1 | Mock up of a Nexus output summary file without dates 2 | Nexus is a trademark of Halliburton 3 | 4 | another title 5 | more waffle 6 | 7 | TIME No. CUM. ------------------------------ 8 | STEP T.S. NWT TIME OIL GAS WATER 9 | NO. REP. ITN HRS kCC kCC kCC 10 | ---- --- -- ---------- ----------- -------- --------- 11 | 1 0 2 0.0000100 0.0 0.0 4.8544E-07 12 | 2 0 1 0.0000300 0.0 0.0 1.4563E-06 13 | 3 0 1 0.0000700 0.0 0.0 3.3E-06 14 | 4 0 1 0.0001500 0.0 0.0 15 | 5 0 1 0.0003100 0.0 16 | 6 0 1 0.0006300 0.0 17 | 7 0 1 0.00127 0.0 18 | 8 0 1 0.00255 0.0 19 | 9 0 1 0.00511 0.0 20 | 10 0 1 0.01023 0.0 21 | 11 0 1 0.02047 0.0 22 | 12 0 1 0.04095 0.0 23 | 13 0 1 0.08191 0.0 24 | 14 0 1 0.164 0.0 25 | 15 0 1 0.328 0.0 26 | 16 0 1 0.662 0.0 27 | 17 0 1 1.331 0.0 28 | 18 0 1 2.000 0.0 29 | 19 0 1 3.000 0.0 30 | 20 0 1 24.000 0.0 31 | 21 0 1 36.000 0.0 -------------------------------------------------------------------------------- /tests/test_data/facies.ib: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /tests/test_data/fault_1.inc: -------------------------------------------------------------------------------- 1 | MULT TX ALL MINUS MULT 2 | GRID ROOT 3 | FNAME fault_1 4 | 2 2 2 2 1 3 1.000000 5 | 6 | -------------------------------------------------------------------------------- /tests/test_data/fault_2.inc: -------------------------------------------------------------------------------- 1 | MULT TX ALL MINUS MULT 2 | GRID ROOT 3 | FNAME fault_2 4 | 2 2 3 3 1 3 2.000000 -------------------------------------------------------------------------------- /tests/test_data/fault_3.inc: -------------------------------------------------------------------------------- 1 | MULT TX ALL MINUS MULT 2 | GRID ROOT 3 | FNAME fault_3 4 | 3 3 4 4 1 3 1.000000 5 | 3 3 5 5 1 3 2.000000 -------------------------------------------------------------------------------- /tests/test_data/logs.las: -------------------------------------------------------------------------------- 1 | ~VERSION INFORMATION 2 | VERS. 2.0 : CWLS LOG ASCII STANDARD -VERSION 2.0 3 | WRAP. NO : ONE LINE PER DEPTH STEP 4 | ~WELL INFORMATION 5 | #MNEM.UNIT DATA DESCRIPTION 6 | #----- ----- ---------- ------------------------- 7 | STRT .M 1000.0000 :START DEPTH 8 | STOP .M 1010.1500 :STOP DEPTH 9 | STEP .M -0.1250 :STEP 10 | NULL . -999.25 :NULL VALUE 11 | COMP . Company_foo :COMPANY 12 | WELL . Well_bar :WELL 13 | FLD . Field_baz :FIELD 14 | LOC . 12-34-12-34W5M :LOCATION 15 | SRVC . ANY LOGGING COMPANY INC. :SERVICE COMPANY 16 | DATE . 03-MAY-20 :LOG DATE 17 | UWI . 123456789 :UNIQUE WELL ID 18 | ~CURVE INFORMATION 19 | #MNEM.UNIT API CODES CURVE DESCRIPTION 20 | #------------------ ------------ ------------------------- 21 | DEPT .M : 1 DEPTH 22 | DT .US/M 60 520 32 00 : 2 SONIC TRANSIT TIME 23 | RHOB .G/CM3 45 350 01 00 : 3 BULK DENSITY 24 | NPHI .V/V 42 890 00 00 : 4 NEUTRON POROSITY 25 | SFLU .OHMM 07 220 04 00 : 5 SHALLOW RESISTIVITY 26 | SFLA .OHMM 07 222 01 00 : 6 SHALLOW RESISTIVITY 27 | ILM .OHMM 07 120 44 00 : 7 MEDIUM RESISTIVITY 28 | ILD .OHMM 07 120 46 00 : 8 DEEP RESISTIVITY 29 | ~PARAMETER INFORMATION 30 | #MNEM.UNIT VALUE DESCRIPTION 31 | #-------------- ---------------- ----------------------------------------------- 32 | MUD . GEL CHEM : MUD TYPE 33 | BHT .DEGC 35.5000 : BOTTOM HOLE TEMPERATURE 34 | BS .MM 200.0000 : BIT SIZE 35 | FD .K/M3 1000.0000 : FLUID DENSITY 36 | MATR . SAND : NEUTRON MATRIX 37 | MDEN . 2710.0000 : LOGGING MATRIX DENSITY 38 | RMF .OHMM 0.2160 : MUD FILTRATE RESISTIVITY 39 | DFD .K/M3 1525.0000 : DRILL FLUID DENSITY 40 | ~A DEPTH DT RHOB NPHI SFLU SFLA ILM ILD 41 | 1000.000 123.450 2.550 0.450 123.450 123.450 -999.25 105.600 42 | 1010.000 124.000 2.550 0.450 123.450 123.450 -999.25 105.600 43 | 1010.150 125.000 -999.25 0.450 123.450 123.450 -999.25 105.600 44 | -------------------------------------------------------------------------------- /tests/test_data/ntg_355.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/ntg_355.db -------------------------------------------------------------------------------- /tests/test_data/wren/ACTIVE: -------------------------------------------------------------------------------- 1 | ! Data written by write_array_to_ascii_file() python function 2 | ! Extent of array is: [8, 5, 3] 3 | ! Maximum 20 data items per line 4 | 5 | 1 1 1 1 1 1 1 1 6 | 7 | 1 1 1 1 1 1 1 1 8 | 9 | 1 1 0 0 0 1 1 1 10 | 11 | 1 1 1 1 1 1 1 1 12 | 13 | 1 1 1 1 1 1 1 1 14 | 15 | 1 1 1 1 1 1 1 1 16 | 17 | 1 1 1 1 1 1 1 1 18 | 19 | 1 1 0 0 0 1 1 1 20 | 21 | 1 1 1 1 1 1 1 1 22 | 23 | 1 1 1 1 1 1 1 1 24 | 25 | 1 1 1 0 0 1 1 1 26 | 27 | 1 1 1 0 0 1 1 1 28 | 29 | 1 1 0 0 0 1 1 1 30 | 31 | 1 1 1 0 0 1 1 1 32 | 33 | 1 1 1 0 0 1 1 1 34 | -------------------------------------------------------------------------------- /tests/test_data/wren/INJECT_wellspec: -------------------------------------------------------------------------------- 1 | METRIC 2 | 3 | WELLSPEC INJECT 4 | IW JW L RADW 5 | 1 1 1 0.100 6 | 1 1 2 0.100 7 | 1 1 3 0.100 8 | METRIC 9 | 10 | WELLSPEC INJECT 11 | IW JW L RADW 12 | 1 1 1 0.100 13 | 1 1 2 0.100 14 | 1 1 3 0.100 15 | -------------------------------------------------------------------------------- /tests/test_data/wren/NETGRS_r_0: -------------------------------------------------------------------------------- 1 | ! Data written by write_array_to_ascii_file() python function 2 | ! Extent of array is: [8, 5, 3] 3 | ! Maximum 20 data items per line 4 | 5 | 0.757 0.860 0.746 0.653 0.620 0.782 0.701 0.895 6 | 7 | 0.684 0.679 0.840 0.708 0.673 0.748 0.688 0.818 8 | 9 | 0.861 0.628 0.813 0.893 0.723 0.623 0.685 0.674 10 | 11 | 0.667 0.825 0.832 0.601 0.709 0.728 0.691 0.880 12 | 13 | 0.687 0.851 0.626 0.791 0.799 0.666 0.652 0.725 14 | 15 | 0.755 0.848 0.814 0.625 0.641 0.671 0.858 0.884 16 | 17 | 0.895 0.701 0.630 0.803 0.865 0.773 0.714 0.681 18 | 19 | 0.639 0.862 0.858 0.763 0.604 0.858 0.784 0.764 20 | 21 | 0.729 0.833 0.806 0.819 0.882 0.626 0.802 0.900 22 | 23 | 0.633 0.605 0.716 0.735 0.690 0.665 0.801 0.801 24 | 25 | 0.788 0.804 0.818 0.803 0.772 0.658 0.855 0.720 26 | 27 | 0.856 0.771 0.699 0.886 0.759 0.856 0.764 0.647 28 | 29 | 0.714 0.703 0.661 0.826 0.812 0.634 0.724 0.803 30 | 31 | 0.865 0.768 0.757 0.808 0.678 0.828 0.815 0.834 32 | 33 | 0.747 0.669 0.869 0.791 0.764 0.890 0.610 0.763 34 | -------------------------------------------------------------------------------- /tests/test_data/wren/NETGRS_r_1: -------------------------------------------------------------------------------- 1 | ! Data written by write_array_to_ascii_file() python function 2 | ! Extent of array is: [8, 5, 3] 3 | ! Maximum 20 data items per line 4 | 5 | 0.672 0.760 0.871 0.878 0.682 0.771 0.673 0.640 6 | 7 | 0.828 0.704 0.729 0.872 0.733 0.850 0.741 0.691 8 | 9 | 0.763 0.863 0.747 0.784 0.690 0.691 0.606 0.608 10 | 11 | 0.765 0.717 0.805 0.813 0.624 0.711 0.899 0.803 12 | 13 | 0.898 0.740 0.686 0.854 0.840 0.624 0.664 0.600 14 | 15 | 0.787 0.768 0.833 0.873 0.642 0.646 0.881 0.810 16 | 17 | 0.660 0.812 0.676 0.751 0.851 0.758 0.674 0.807 18 | 19 | 0.829 0.899 0.783 0.633 0.682 0.829 0.736 0.893 20 | 21 | 0.618 0.694 0.790 0.637 0.655 0.858 0.654 0.805 22 | 23 | 0.888 0.702 0.871 0.766 0.689 0.765 0.895 0.748 24 | 25 | 0.807 0.756 0.774 0.600 0.697 0.792 0.724 0.765 26 | 27 | 0.809 0.643 0.893 0.721 0.879 0.617 0.720 0.609 28 | 29 | 0.703 0.751 0.897 0.734 0.849 0.869 0.695 0.857 30 | 31 | 0.627 0.655 0.664 0.895 0.682 0.837 0.874 0.765 32 | 33 | 0.862 0.831 0.626 0.619 0.642 0.751 0.862 0.761 34 | -------------------------------------------------------------------------------- /tests/test_data/wren/NETGRS_r_2: -------------------------------------------------------------------------------- 1 | ! Data written by write_array_to_ascii_file() python function 2 | ! Extent of array is: [8, 5, 3] 3 | ! Maximum 20 data items per line 4 | 5 | 0.735 0.625 0.887 0.674 0.841 0.812 0.787 0.807 6 | 7 | 0.730 0.811 0.865 0.718 0.613 0.621 0.900 0.660 8 | 9 | 0.863 0.835 0.712 0.843 0.819 0.742 0.817 0.723 10 | 11 | 0.891 0.747 0.867 0.684 0.854 0.778 0.758 0.883 12 | 13 | 0.768 0.602 0.897 0.733 0.607 0.815 0.651 0.618 14 | 15 | 0.620 0.766 0.726 0.724 0.893 0.649 0.706 0.673 16 | 17 | 0.636 0.825 0.833 0.880 0.698 0.742 0.821 0.803 18 | 19 | 0.736 0.660 0.692 0.870 0.619 0.717 0.615 0.824 20 | 21 | 0.625 0.777 0.816 0.845 0.855 0.747 0.833 0.710 22 | 23 | 0.895 0.839 0.784 0.809 0.778 0.838 0.748 0.683 24 | 25 | 0.820 0.752 0.695 0.748 0.717 0.875 0.643 0.623 26 | 27 | 0.719 0.713 0.622 0.868 0.711 0.769 0.651 0.662 28 | 29 | 0.744 0.742 0.810 0.702 0.804 0.689 0.809 0.690 30 | 31 | 0.848 0.656 0.728 0.822 0.749 0.804 0.754 0.832 32 | 33 | 0.633 0.881 0.784 0.671 0.811 0.800 0.828 0.698 34 | -------------------------------------------------------------------------------- /tests/test_data/wren/PORO_r_0: -------------------------------------------------------------------------------- 1 | ! Data written by write_array_to_ascii_file() python function 2 | ! Extent of array is: [8, 5, 3] 3 | ! Maximum 20 data items per line 4 | 5 | 0.266 0.273 0.250 0.265 0.205 0.265 0.264 0.254 6 | 7 | 0.285 0.289 0.244 0.265 0.241 0.244 0.292 0.250 8 | 9 | 0.277 0.238 0.211 0.293 0.289 0.287 0.255 0.235 10 | 11 | 0.279 0.215 0.226 0.256 0.293 0.217 0.234 0.238 12 | 13 | 0.287 0.234 0.292 0.228 0.290 0.219 0.240 0.252 14 | 15 | 0.248 0.209 0.288 0.277 0.217 0.261 0.262 0.291 16 | 17 | 0.256 0.229 0.243 0.227 0.239 0.221 0.273 0.258 18 | 19 | 0.294 0.267 0.290 0.211 0.293 0.233 0.288 0.281 20 | 21 | 0.242 0.214 0.235 0.271 0.238 0.237 0.251 0.264 22 | 23 | 0.254 0.270 0.207 0.288 0.295 0.229 0.202 0.201 24 | 25 | 0.232 0.247 0.283 0.290 0.213 0.226 0.253 0.291 26 | 27 | 0.240 0.213 0.234 0.256 0.258 0.275 0.298 0.236 28 | 29 | 0.268 0.245 0.214 0.225 0.267 0.283 0.296 0.233 30 | 31 | 0.211 0.262 0.269 0.298 0.225 0.285 0.288 0.294 32 | 33 | 0.276 0.260 0.263 0.253 0.294 0.215 0.215 0.265 34 | -------------------------------------------------------------------------------- /tests/test_data/wren/PORO_r_1: -------------------------------------------------------------------------------- 1 | ! Data written by write_array_to_ascii_file() python function 2 | ! Extent of array is: [8, 5, 3] 3 | ! Maximum 20 data items per line 4 | 5 | 0.257 0.248 0.254 0.284 0.286 0.212 0.210 0.216 6 | 7 | 0.235 0.232 0.252 0.219 0.264 0.253 0.209 0.276 8 | 9 | 0.268 0.211 0.203 0.258 0.203 0.288 0.282 0.283 10 | 11 | 0.217 0.283 0.206 0.243 0.247 0.293 0.295 0.255 12 | 13 | 0.254 0.245 0.221 0.266 0.265 0.245 0.274 0.248 14 | 15 | 0.295 0.252 0.290 0.201 0.253 0.270 0.253 0.292 16 | 17 | 0.237 0.247 0.290 0.251 0.214 0.289 0.276 0.254 18 | 19 | 0.264 0.228 0.219 0.245 0.299 0.292 0.277 0.297 20 | 21 | 0.250 0.262 0.203 0.226 0.279 0.201 0.274 0.263 22 | 23 | 0.206 0.223 0.280 0.225 0.200 0.271 0.238 0.266 24 | 25 | 0.235 0.276 0.227 0.237 0.292 0.276 0.273 0.207 26 | 27 | 0.297 0.268 0.297 0.263 0.255 0.255 0.244 0.295 28 | 29 | 0.236 0.254 0.289 0.279 0.215 0.273 0.230 0.275 30 | 31 | 0.213 0.220 0.249 0.263 0.256 0.212 0.242 0.285 32 | 33 | 0.277 0.223 0.262 0.233 0.283 0.287 0.230 0.219 34 | -------------------------------------------------------------------------------- /tests/test_data/wren/PORO_r_2: -------------------------------------------------------------------------------- 1 | ! Data written by write_array_to_ascii_file() python function 2 | ! Extent of array is: [8, 5, 3] 3 | ! Maximum 20 data items per line 4 | 5 | 0.210 0.297 0.262 0.217 0.209 0.222 0.283 0.269 6 | 7 | 0.228 0.220 0.274 0.294 0.222 0.246 0.203 0.250 8 | 9 | 0.248 0.201 0.203 0.228 0.204 0.289 0.263 0.269 10 | 11 | 0.230 0.280 0.263 0.211 0.289 0.288 0.235 0.202 12 | 13 | 0.262 0.210 0.269 0.233 0.232 0.245 0.238 0.298 14 | 15 | 0.268 0.297 0.220 0.270 0.206 0.232 0.243 0.200 16 | 17 | 0.250 0.218 0.234 0.221 0.232 0.259 0.228 0.241 18 | 19 | 0.250 0.272 0.255 0.277 0.275 0.218 0.272 0.210 20 | 21 | 0.252 0.217 0.263 0.287 0.295 0.228 0.205 0.221 22 | 23 | 0.223 0.252 0.290 0.269 0.244 0.286 0.298 0.291 24 | 25 | 0.231 0.225 0.248 0.233 0.278 0.248 0.237 0.297 26 | 27 | 0.221 0.239 0.210 0.231 0.285 0.242 0.230 0.256 28 | 29 | 0.293 0.271 0.270 0.227 0.218 0.209 0.208 0.218 30 | 31 | 0.284 0.234 0.230 0.293 0.213 0.234 0.290 0.286 32 | 33 | 0.267 0.276 0.248 0.250 0.262 0.209 0.277 0.229 34 | -------------------------------------------------------------------------------- /tests/test_data/wren/PRODUCE_wellspec: -------------------------------------------------------------------------------- 1 | METRIC 2 | 3 | WELLSPEC PRODUCE 4 | IW JW L RADW 5 | 8 5 1 0.100 6 | 8 5 2 0.100 7 | 8 5 3 0.100 8 | METRIC 9 | 10 | WELLSPEC PRODUCE 11 | IW JW L RADW 12 | 8 5 1 0.100 13 | 8 5 2 0.100 14 | 8 5 3 0.100 15 | -------------------------------------------------------------------------------- /tests/test_data/wren/perm_direction_IJ_r_0: -------------------------------------------------------------------------------- 1 | ! Data written by write_array_to_ascii_file() python function 2 | ! Extent of array is: [8, 5, 3] 3 | ! Maximum 20 data items per line 4 | 5 | 406.188 388.964 240.802 250.210 321.136 207.970 455.930 384.254 6 | 7 | 432.446 276.802 458.855 242.507 450.828 378.558 483.550 249.104 8 | 9 | 304.357 210.370 408.356 338.179 448.052 333.326 458.980 298.831 10 | 11 | 451.653 378.805 374.333 489.930 460.143 387.984 312.164 436.670 12 | 13 | 413.673 480.749 264.525 320.534 450.285 372.312 402.157 400.677 14 | 15 | 274.189 234.096 225.565 417.329 428.317 222.705 395.926 449.719 16 | 17 | 280.667 339.008 469.220 209.456 399.338 291.589 417.494 335.071 18 | 19 | 395.661 247.546 203.177 406.427 245.915 205.418 236.050 423.382 20 | 21 | 483.303 294.738 472.452 363.897 241.627 460.481 265.926 300.903 22 | 23 | 330.454 333.034 302.911 280.413 350.188 378.842 360.227 355.558 24 | 25 | 210.944 252.884 397.760 243.669 387.605 240.400 345.994 229.760 26 | 27 | 434.519 445.110 346.927 243.831 262.835 455.581 419.225 486.374 28 | 29 | 205.530 280.583 490.341 435.047 455.601 394.864 288.075 390.227 30 | 31 | 203.830 414.243 287.573 495.052 396.504 279.110 289.955 369.494 32 | 33 | 427.186 328.196 359.126 491.991 426.763 210.444 360.475 326.038 34 | -------------------------------------------------------------------------------- /tests/test_data/wren/perm_direction_IJ_r_1: -------------------------------------------------------------------------------- 1 | ! Data written by write_array_to_ascii_file() python function 2 | ! Extent of array is: [8, 5, 3] 3 | ! Maximum 20 data items per line 4 | 5 | 295.443 224.866 452.787 269.706 443.409 316.043 442.024 403.533 6 | 7 | 345.699 217.335 447.664 456.940 230.103 404.896 335.595 363.342 8 | 9 | 313.883 347.994 260.491 234.009 255.194 347.701 225.871 390.524 10 | 11 | 283.514 259.259 219.738 266.729 203.643 340.892 229.139 310.149 12 | 13 | 333.675 450.072 206.685 452.359 203.891 450.501 249.572 221.983 14 | 15 | 265.834 329.444 329.894 282.596 401.376 477.266 427.174 240.903 16 | 17 | 217.714 494.986 416.696 216.601 259.611 376.258 207.007 355.124 18 | 19 | 498.957 403.531 213.928 321.207 409.318 316.418 259.154 462.378 20 | 21 | 296.250 313.120 320.659 498.500 293.776 231.447 253.015 229.897 22 | 23 | 249.678 340.020 253.701 287.939 462.787 448.531 333.770 432.015 24 | 25 | 222.609 339.687 376.123 355.326 446.601 268.603 415.075 401.412 26 | 27 | 271.318 201.119 232.614 331.611 358.692 326.680 262.815 228.678 28 | 29 | 309.126 358.971 487.093 376.096 363.810 448.510 425.790 361.701 30 | 31 | 458.532 292.757 325.429 248.110 225.880 403.394 407.918 353.703 32 | 33 | 239.014 404.948 251.773 329.804 492.241 334.739 400.092 458.903 34 | -------------------------------------------------------------------------------- /tests/test_data/wren/perm_direction_IJ_r_2: -------------------------------------------------------------------------------- 1 | ! Data written by write_array_to_ascii_file() python function 2 | ! Extent of array is: [8, 5, 3] 3 | ! Maximum 20 data items per line 4 | 5 | 407.548 231.389 317.844 430.664 449.405 279.071 220.791 360.950 6 | 7 | 335.398 355.065 432.004 457.890 314.623 308.849 232.904 408.889 8 | 9 | 302.216 266.402 325.104 268.975 298.943 486.737 337.882 444.010 10 | 11 | 435.587 211.763 476.756 366.940 242.774 296.314 405.951 245.053 12 | 13 | 367.226 239.160 439.484 338.259 330.278 398.672 382.077 484.889 14 | 15 | 442.263 400.788 294.336 498.465 429.488 263.390 224.700 267.567 16 | 17 | 348.770 244.414 216.210 234.615 317.761 392.265 356.106 346.738 18 | 19 | 354.678 449.504 262.359 351.244 273.986 413.159 469.449 478.265 20 | 21 | 482.740 390.148 289.634 318.642 221.376 201.879 426.819 232.271 22 | 23 | 332.731 334.454 392.495 378.474 462.024 414.360 202.393 396.358 24 | 25 | 463.410 469.081 307.656 374.991 238.333 373.336 408.036 275.939 26 | 27 | 212.728 468.585 463.963 448.318 235.194 475.227 484.030 389.967 28 | 29 | 367.498 421.427 450.499 334.743 396.398 215.247 360.630 421.986 30 | 31 | 274.958 493.401 426.351 405.473 284.815 352.081 212.476 228.309 32 | 33 | 276.922 288.993 486.137 489.065 464.309 347.921 366.222 417.764 34 | -------------------------------------------------------------------------------- /tests/test_data/wren/perm_direction_K_r_0: -------------------------------------------------------------------------------- 1 | ! Data written by write_array_to_ascii_file() python function 2 | ! Extent of array is: [8, 5, 3] 3 | ! Maximum 20 data items per line 4 | 5 | 46.899 29.276 33.478 23.295 49.044 21.786 47.421 21.211 6 | 7 | 26.602 32.261 24.949 36.948 43.975 46.486 47.211 38.488 8 | 9 | 35.831 45.253 32.763 35.179 39.162 36.833 40.725 38.066 10 | 11 | 21.078 40.005 28.902 44.646 35.231 37.066 47.396 22.093 12 | 13 | 25.835 44.324 38.728 35.299 35.545 40.935 37.257 34.871 14 | 15 | 37.021 24.896 29.504 31.794 41.220 39.435 46.203 47.494 16 | 17 | 22.519 29.197 28.231 26.436 25.293 42.919 28.724 40.191 18 | 19 | 43.887 48.041 33.617 32.098 49.573 24.811 44.189 37.835 20 | 21 | 34.924 45.454 47.651 29.835 42.540 28.178 49.057 32.223 22 | 23 | 36.464 42.848 31.173 30.790 32.651 23.283 41.880 30.856 24 | 25 | 37.392 49.430 26.607 35.245 23.351 26.840 22.171 25.066 26 | 27 | 26.334 20.448 43.975 32.741 38.413 30.251 39.766 40.324 28 | 29 | 23.771 23.596 23.959 30.748 28.246 45.414 30.121 47.212 30 | 31 | 22.902 48.560 40.783 34.193 26.755 22.463 49.776 36.451 32 | 33 | 46.956 40.779 36.858 26.346 26.527 49.426 27.556 29.252 34 | -------------------------------------------------------------------------------- /tests/test_data/wren/perm_direction_K_r_1: -------------------------------------------------------------------------------- 1 | ! Data written by write_array_to_ascii_file() python function 2 | ! Extent of array is: [8, 5, 3] 3 | ! Maximum 20 data items per line 4 | 5 | 32.538 46.025 39.035 33.372 30.721 47.025 28.492 24.420 6 | 7 | 25.318 43.157 48.695 45.520 38.876 22.517 20.561 22.903 8 | 9 | 33.748 25.182 46.437 35.909 28.440 40.017 30.253 46.248 10 | 11 | 42.559 47.770 49.164 24.062 28.217 28.091 38.182 37.783 12 | 13 | 26.401 38.882 32.118 37.420 47.905 29.333 33.471 49.213 14 | 15 | 29.992 45.998 28.160 27.440 20.432 39.664 23.739 30.708 16 | 17 | 22.946 48.910 29.220 38.083 29.820 47.207 42.949 37.842 18 | 19 | 34.324 40.274 33.849 42.245 39.308 49.424 25.992 47.654 20 | 21 | 36.014 24.605 20.149 39.708 46.975 37.822 27.858 46.585 22 | 23 | 41.440 44.901 45.286 32.657 47.059 40.948 20.674 41.363 24 | 25 | 22.861 40.367 20.432 29.470 39.199 28.292 22.383 38.506 26 | 27 | 34.448 49.323 40.361 38.809 24.371 31.083 22.057 41.679 28 | 29 | 46.548 49.186 45.776 20.621 21.054 23.852 40.196 42.850 30 | 31 | 39.202 25.259 25.870 32.346 45.036 26.793 34.847 45.096 32 | 33 | 45.170 25.704 47.610 30.404 40.795 30.986 40.477 28.697 34 | -------------------------------------------------------------------------------- /tests/test_data/wren/perm_direction_K_r_2: -------------------------------------------------------------------------------- 1 | ! Data written by write_array_to_ascii_file() python function 2 | ! Extent of array is: [8, 5, 3] 3 | ! Maximum 20 data items per line 4 | 5 | 38.878 38.414 34.326 27.635 20.911 45.530 46.557 24.620 6 | 7 | 32.452 47.675 42.851 43.877 43.116 30.333 30.215 39.108 8 | 9 | 38.840 38.312 29.630 30.717 23.469 20.685 22.410 46.008 10 | 11 | 37.653 49.229 29.059 39.151 38.892 40.280 49.919 30.094 12 | 13 | 21.312 24.031 27.464 34.458 43.960 48.201 40.984 24.275 14 | 15 | 23.962 47.823 34.369 36.921 38.429 36.792 36.860 42.066 16 | 17 | 22.925 33.706 36.481 26.491 27.039 40.307 24.205 42.283 18 | 19 | 26.101 35.454 25.155 33.679 27.698 46.268 37.834 27.728 20 | 21 | 46.677 36.230 48.937 28.371 24.169 43.949 36.104 45.724 22 | 23 | 34.235 47.957 21.999 34.792 40.028 35.712 41.981 33.567 24 | 25 | 24.095 31.583 24.534 26.713 31.113 49.787 48.145 32.943 26 | 27 | 28.525 32.868 42.617 49.643 41.168 23.182 29.600 43.075 28 | 29 | 48.810 23.580 25.406 44.401 38.522 42.277 43.441 20.566 30 | 31 | 36.984 42.254 30.754 28.584 25.950 22.557 34.487 43.680 32 | 33 | 45.739 27.474 43.286 44.951 31.144 30.356 22.535 23.304 34 | -------------------------------------------------------------------------------- /tests/test_data/wren/wren.epc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren.epc -------------------------------------------------------------------------------- /tests/test_data/wren/wren.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren.h5 -------------------------------------------------------------------------------- /tests/test_data/wren/wren0.vdb/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /tests/test_data/wren/wren0.vdb/wren0/ALIAS/alias.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren0.vdb/wren0/ALIAS/alias.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren0.vdb/wren0/INIT/MAPDATA/IROOTBV.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren0.vdb/wren0/INIT/MAPDATA/IROOTBV.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren0.vdb/wren0/INIT/MAPDATA/IROOTDAD.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren0.vdb/wren0/INIT/MAPDATA/IROOTDAD.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren0.vdb/wren0/INIT/MAPDATA/IROOTDEADCELL.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren0.vdb/wren0/INIT/MAPDATA/IROOTDEADCELL.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren0.vdb/wren0/INIT/MAPDATA/IROOTDXC.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren0.vdb/wren0/INIT/MAPDATA/IROOTDXC.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren0.vdb/wren0/INIT/MAPDATA/IROOTDYC.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren0.vdb/wren0/INIT/MAPDATA/IROOTDYC.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren0.vdb/wren0/INIT/MAPDATA/IROOTDZC.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren0.vdb/wren0/INIT/MAPDATA/IROOTDZC.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren0.vdb/wren0/INIT/MAPDATA/IROOTDZN.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren0.vdb/wren0/INIT/MAPDATA/IROOTDZN.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren0.vdb/wren0/INIT/MAPDATA/IROOTKH.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren0.vdb/wren0/INIT/MAPDATA/IROOTKH.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren0.vdb/wren0/INIT/MAPDATA/IROOTKID.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren0.vdb/wren0/INIT/MAPDATA/IROOTKID.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren0.vdb/wren0/INIT/MAPDATA/IROOTMDEP.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren0.vdb/wren0/INIT/MAPDATA/IROOTMDEP.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren0.vdb/wren0/INIT/MAPDATA/IROOTPVR.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren0.vdb/wren0/INIT/MAPDATA/IROOTPVR.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren0.vdb/wren0/INIT/MAPDATA/IROOTUID.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren0.vdb/wren0/INIT/MAPDATA/IROOTUID.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren0.vdb/wren0/INIT/MAPDATA/IROOTUNPACK.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren0.vdb/wren0/INIT/MAPDATA/IROOTUNPACK.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren0.vdb/wren0/INIT/ROOT_corp.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren0.vdb/wren0/INIT/ROOT_corp.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren0.vdb/wren0/INIT/ROOT_faultInfo.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren0.vdb/wren0/INIT/ROOT_faultInfo.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren0.vdb/wren0/INIT/nsc_conn.fbin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren0.vdb/wren0/INIT/nsc_conn.fbin -------------------------------------------------------------------------------- /tests/test_data/wren/wren0.vdb/wren0/RECUR/MAPDATA/RROOT_0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren0.vdb/wren0/RECUR/MAPDATA/RROOT_0.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren0.vdb/wren0/RECUR/MAPDATA/RROOT_22.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren0.vdb/wren0/RECUR/MAPDATA/RROOT_22.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren0.vdb/wren0/RECUR/MAPDATA/RROOT_34.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren0.vdb/wren0/RECUR/MAPDATA/RROOT_34.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren0.vdb/wren0/RECUR/MAPDATA/RROOT_4.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren0.vdb/wren0/RECUR/MAPDATA/RROOT_4.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren0.vdb/wren0/RECUR/perfts.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren0.vdb/wren0/RECUR/perfts.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren0.vdb/wren0/ThresholdSizeCache.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /tests/test_data/wren/wren0.vdb/wren0/WELIST/welist.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren0.vdb/wren0/WELIST/welist.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren0.vdb/wren0/case.ctrl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren0.vdb/wren0/case.ctrl -------------------------------------------------------------------------------- /tests/test_data/wren/wren1.vdb/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /tests/test_data/wren/wren1.vdb/wren1/ALIAS/alias.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren1.vdb/wren1/ALIAS/alias.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren1.vdb/wren1/INIT/MAPDATA/IROOTBV.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren1.vdb/wren1/INIT/MAPDATA/IROOTBV.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren1.vdb/wren1/INIT/MAPDATA/IROOTDAD.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren1.vdb/wren1/INIT/MAPDATA/IROOTDAD.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren1.vdb/wren1/INIT/MAPDATA/IROOTDEADCELL.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren1.vdb/wren1/INIT/MAPDATA/IROOTDEADCELL.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren1.vdb/wren1/INIT/MAPDATA/IROOTDXC.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren1.vdb/wren1/INIT/MAPDATA/IROOTDXC.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren1.vdb/wren1/INIT/MAPDATA/IROOTDYC.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren1.vdb/wren1/INIT/MAPDATA/IROOTDYC.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren1.vdb/wren1/INIT/MAPDATA/IROOTDZC.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren1.vdb/wren1/INIT/MAPDATA/IROOTDZC.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren1.vdb/wren1/INIT/MAPDATA/IROOTDZN.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren1.vdb/wren1/INIT/MAPDATA/IROOTDZN.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren1.vdb/wren1/INIT/MAPDATA/IROOTKH.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren1.vdb/wren1/INIT/MAPDATA/IROOTKH.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren1.vdb/wren1/INIT/MAPDATA/IROOTKID.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren1.vdb/wren1/INIT/MAPDATA/IROOTKID.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren1.vdb/wren1/INIT/MAPDATA/IROOTMDEP.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren1.vdb/wren1/INIT/MAPDATA/IROOTMDEP.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren1.vdb/wren1/INIT/MAPDATA/IROOTPVR.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren1.vdb/wren1/INIT/MAPDATA/IROOTPVR.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren1.vdb/wren1/INIT/MAPDATA/IROOTUID.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren1.vdb/wren1/INIT/MAPDATA/IROOTUID.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren1.vdb/wren1/INIT/MAPDATA/IROOTUNPACK.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren1.vdb/wren1/INIT/MAPDATA/IROOTUNPACK.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren1.vdb/wren1/INIT/ROOT_corp.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren1.vdb/wren1/INIT/ROOT_corp.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren1.vdb/wren1/INIT/ROOT_faultInfo.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren1.vdb/wren1/INIT/ROOT_faultInfo.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren1.vdb/wren1/INIT/nsc_conn.fbin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren1.vdb/wren1/INIT/nsc_conn.fbin -------------------------------------------------------------------------------- /tests/test_data/wren/wren1.vdb/wren1/RECUR/MAPDATA/RROOT_0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren1.vdb/wren1/RECUR/MAPDATA/RROOT_0.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren1.vdb/wren1/RECUR/MAPDATA/RROOT_22.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren1.vdb/wren1/RECUR/MAPDATA/RROOT_22.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren1.vdb/wren1/RECUR/MAPDATA/RROOT_34.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren1.vdb/wren1/RECUR/MAPDATA/RROOT_34.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren1.vdb/wren1/RECUR/MAPDATA/RROOT_4.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren1.vdb/wren1/RECUR/MAPDATA/RROOT_4.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren1.vdb/wren1/RECUR/perfts.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren1.vdb/wren1/RECUR/perfts.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren1.vdb/wren1/ThresholdSizeCache.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /tests/test_data/wren/wren1.vdb/wren1/WELIST/welist.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren1.vdb/wren1/WELIST/welist.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren1.vdb/wren1/case.ctrl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren1.vdb/wren1/case.ctrl -------------------------------------------------------------------------------- /tests/test_data/wren/wren2.vdb/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /tests/test_data/wren/wren2.vdb/wren2/ALIAS/alias.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren2.vdb/wren2/ALIAS/alias.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren2.vdb/wren2/INIT/MAPDATA/IROOTBV.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren2.vdb/wren2/INIT/MAPDATA/IROOTBV.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren2.vdb/wren2/INIT/MAPDATA/IROOTDAD.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren2.vdb/wren2/INIT/MAPDATA/IROOTDAD.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren2.vdb/wren2/INIT/MAPDATA/IROOTDEADCELL.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren2.vdb/wren2/INIT/MAPDATA/IROOTDEADCELL.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren2.vdb/wren2/INIT/MAPDATA/IROOTDXC.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren2.vdb/wren2/INIT/MAPDATA/IROOTDXC.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren2.vdb/wren2/INIT/MAPDATA/IROOTDYC.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren2.vdb/wren2/INIT/MAPDATA/IROOTDYC.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren2.vdb/wren2/INIT/MAPDATA/IROOTDZC.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren2.vdb/wren2/INIT/MAPDATA/IROOTDZC.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren2.vdb/wren2/INIT/MAPDATA/IROOTDZN.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren2.vdb/wren2/INIT/MAPDATA/IROOTDZN.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren2.vdb/wren2/INIT/MAPDATA/IROOTKH.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren2.vdb/wren2/INIT/MAPDATA/IROOTKH.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren2.vdb/wren2/INIT/MAPDATA/IROOTKID.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren2.vdb/wren2/INIT/MAPDATA/IROOTKID.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren2.vdb/wren2/INIT/MAPDATA/IROOTMDEP.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren2.vdb/wren2/INIT/MAPDATA/IROOTMDEP.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren2.vdb/wren2/INIT/MAPDATA/IROOTPVR.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren2.vdb/wren2/INIT/MAPDATA/IROOTPVR.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren2.vdb/wren2/INIT/MAPDATA/IROOTUID.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren2.vdb/wren2/INIT/MAPDATA/IROOTUID.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren2.vdb/wren2/INIT/MAPDATA/IROOTUNPACK.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren2.vdb/wren2/INIT/MAPDATA/IROOTUNPACK.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren2.vdb/wren2/INIT/ROOT_corp.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren2.vdb/wren2/INIT/ROOT_corp.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren2.vdb/wren2/INIT/ROOT_faultInfo.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren2.vdb/wren2/INIT/ROOT_faultInfo.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren2.vdb/wren2/INIT/nsc_conn.fbin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren2.vdb/wren2/INIT/nsc_conn.fbin -------------------------------------------------------------------------------- /tests/test_data/wren/wren2.vdb/wren2/RECUR/MAPDATA/RROOT_0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren2.vdb/wren2/RECUR/MAPDATA/RROOT_0.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren2.vdb/wren2/RECUR/MAPDATA/RROOT_22.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren2.vdb/wren2/RECUR/MAPDATA/RROOT_22.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren2.vdb/wren2/RECUR/MAPDATA/RROOT_34.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren2.vdb/wren2/RECUR/MAPDATA/RROOT_34.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren2.vdb/wren2/RECUR/MAPDATA/RROOT_4.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren2.vdb/wren2/RECUR/MAPDATA/RROOT_4.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren2.vdb/wren2/RECUR/perfts.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren2.vdb/wren2/RECUR/perfts.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren2.vdb/wren2/ThresholdSizeCache.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /tests/test_data/wren/wren2.vdb/wren2/WELIST/welist.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren2.vdb/wren2/WELIST/welist.bin -------------------------------------------------------------------------------- /tests/test_data/wren/wren2.vdb/wren2/case.ctrl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bp/resqpy/eac9f88cc2c22dbff3b348d1df5302db98e58eeb/tests/test_data/wren/wren2.vdb/wren2/case.ctrl -------------------------------------------------------------------------------- /tests/unit_tests/grid/test_face_functions.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | 4 | from resqpy.grid import Grid 5 | from resqpy.model import Model 6 | 7 | import resqpy.grid._face_functions as ff 8 | 9 | 10 | def test_face_centre_axis_0(basic_regular_grid: Grid): 11 | # Arrange 12 | cell = None 13 | axis = 0 14 | zero_or_one = 0 15 | # yapf: disable 16 | expected_face_centres = np.array([[[[50.0, 25.0, 0.0], [150.0, 25.0, 0.0]], 17 | [[50.0, 75.0, 0.0], [150.0, 75.0, 0.0]]], 18 | [[[50.0, 25.0, 20.0], [150.0, 25.0, 20.0]], 19 | [[50.0, 75.0, 20.0], [150.0, 75.0, 20.0]]]]) # yapf: enable 20 | 21 | # Act & Assert 22 | face_centre = ff.face_centre(basic_regular_grid, cell, axis, zero_or_one) 23 | np.testing.assert_array_almost_equal(face_centre, expected_face_centres) 24 | 25 | 26 | def test_face_centre_axis_1(basic_regular_grid: Grid): 27 | # Arrange 28 | cell = None 29 | axis = 1 30 | zero_or_one = 1 31 | # yapf: disable 32 | expected_face_centres = np.array([[[[50.0, 50.0, 10.0], [150.0, 50.0, 10.0]], 33 | [[50.0, 100.0, 10.0], [150.0, 100.0, 10.0]]], 34 | [[[50.0, 50.0, 30.0], [150.0, 50.0, 30.0]], 35 | [[50.0, 100.0, 30.0], [150.0, 100.0, 30.0]]]]) # yapf: enable 36 | 37 | # Act & Assert 38 | face_centre = ff.face_centre(basic_regular_grid, cell, axis, zero_or_one) 39 | np.testing.assert_array_almost_equal(face_centre, expected_face_centres) 40 | 41 | 42 | def test_face_centre_cell_kji0(basic_regular_grid: Grid): 43 | # Arrange 44 | cell = (0, 0, 0) 45 | axis = 0 46 | 47 | # Act & Assert 48 | zero_or_one = 0 49 | face_centre = ff.face_centre(basic_regular_grid, cell, axis, zero_or_one) 50 | np.testing.assert_array_equal(face_centre, np.array([50.0, 25.0, 0.0])) 51 | 52 | zero_or_one = 1 53 | face_centre = ff.face_centre(basic_regular_grid, cell, axis, zero_or_one) 54 | np.testing.assert_array_almost_equal(face_centre, np.array([50.0, 25.0, 20.0])) 55 | 56 | 57 | def test_face_centre_invalid_axis(example_model_with_properties: Model): 58 | # Arrange 59 | grid = example_model_with_properties.grid() 60 | cell = (1, 1, 1) 61 | axis = 4 62 | zero_or_one = 0 63 | 64 | # Act & Assert 65 | with pytest.raises(ValueError): 66 | ff.face_centre(grid, cell, axis, zero_or_one) 67 | 68 | 69 | @pytest.mark.parametrize( 70 | "cell, expected_face_centres", 71 | # yapf: disable 72 | [((0, 0, 0), np.array([[[50.0, 25.0, 0.0], [50.0, 25.0, 20.0]], 73 | [[50.0, 0.0, 10.0], [50.0, 50.0, 10.0]], 74 | [[0.0, 25.0, 10.0], [100.0, 25.0, 10.0]]])), 75 | ((1, 1, 1), np.array([[[150.0, 75.0, 20.0], [150.0, 75.0, 40.0]], 76 | [[150.0, 50.0, 30.0], [150.0, 100.0, 30.0]], 77 | [[100.0, 75.0, 30.0], [200.0, 75.0, 30.0]]]))]) # yapf: enable 78 | def test_face_centres_kji_01(basic_regular_grid: Grid, cell, expected_face_centres): 79 | # Act & Assert 80 | face_centres = ff.face_centres_kji_01(basic_regular_grid, cell) 81 | np.testing.assert_array_almost_equal(face_centres, expected_face_centres) 82 | -------------------------------------------------------------------------------- /tests/unit_tests/grid/test_parametric_line_geometry.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os 3 | import numpy as np 4 | 5 | import resqpy.model as rq 6 | import resqpy.grid as grr 7 | 8 | 9 | def test_parametric_line_geometry(tmp_path, basic_regular_grid): 10 | # finish converting the regular grid to irregular form 11 | old_model = basic_regular_grid.model 12 | basic_regular_grid.write_hdf5() 13 | basic_regular_grid.create_xml() 14 | basic_grid = grr.Grid(old_model, uuid = basic_regular_grid.uuid) 15 | new_file = os.path.join(tmp_path, os.path.basename(f'{old_model.epc_file[:-4]}_pl_basic.epc')) 16 | new_model = rq.new_model(new_file) 17 | # copy crs 18 | new_model.copy_uuid_from_other_model(old_model, basic_grid.crs_uuid) 19 | # hijack grid 20 | basic_grid.cache_all_geometry_arrays() 21 | basic_grid.model = new_model 22 | # write hdf5 data for grid using paramtric lines option 23 | basic_grid.write_hdf5(use_parametric_lines = True) 24 | # create xml for grid using parametric lines option 25 | basic_grid.create_xml(use_parametric_lines = True) 26 | # store new version of model and reload 27 | new_model.store_epc() 28 | reload_model = rq.Model(new_model.epc_file) 29 | # check reloaded grid 30 | reload_grid = reload_model.grid() 31 | reload_grid.cache_all_geometry_arrays() 32 | # assertions 33 | assert tuple(reload_grid.extent_kji) == tuple(basic_grid.extent_kji) 34 | assert reload_grid.points_cached is not None 35 | assert reload_grid.points_cached.shape == basic_grid.points_cached.shape 36 | np.testing.assert_array_almost_equal(reload_grid.points_cached, basic_grid.points_cached) 37 | 38 | 39 | def test_parametric_line_geometry_faulted(tmp_path, faulted_grid): 40 | # Act 41 | old_model = faulted_grid.model 42 | new_file = os.path.join(tmp_path, os.path.basename(f'{old_model.epc_file[:-4]}_pl_faulted.epc')) 43 | new_model = rq.new_model(new_file) 44 | # copy crs 45 | new_model.copy_uuid_from_other_model(old_model, faulted_grid.crs_uuid) 46 | # hijack grid 47 | faulted_grid.cache_all_geometry_arrays() 48 | faulted_grid.model = new_model 49 | # write hdf5 data for grid using paramtric lines option 50 | faulted_grid.write_hdf5(use_parametric_lines = True) 51 | # create xml for grid using parametric lines option 52 | faulted_grid.create_xml(use_parametric_lines = True) 53 | # store new version of model and reload 54 | new_model.store_epc() 55 | reload_model = rq.Model(new_model.epc_file) 56 | # check reloaded grid 57 | reload_grid = reload_model.grid() 58 | reload_grid.cache_all_geometry_arrays() 59 | # assertions 60 | assert tuple(reload_grid.extent_kji) == tuple(faulted_grid.extent_kji) 61 | assert reload_grid.points_cached is not None 62 | assert reload_grid.points_cached.shape == faulted_grid.points_cached.shape 63 | np.testing.assert_array_almost_equal(reload_grid.points_cached, faulted_grid.points_cached) 64 | -------------------------------------------------------------------------------- /tests/unit_tests/multiprocessing/test_mesh_mp.py: -------------------------------------------------------------------------------- 1 | from resqpy.model import Model 2 | from resqpy.surface import Mesh 3 | from resqpy.multi_processing.wrappers import mesh_mp 4 | from resqpy.multi_processing._multiprocessing import rm_tree 5 | 6 | 7 | def test_mesh_from_regular_grid_column_property_wrapper(small_grid_with_properties): 8 | # Arrange 9 | grid, prop_uuids = small_grid_with_properties 10 | grid_epc = grid.model.epc_file 11 | grid_uuid = grid.uuid 12 | input_index = 0 13 | 14 | # Act 15 | ( 16 | index, 17 | success, 18 | epc_file, 19 | uuid_list, 20 | ) = mesh_mp.mesh_from_regular_grid_column_property_wrapper( 21 | input_index, 22 | "tmp_dir", 23 | grid_epc, 24 | grid_uuid, 25 | prop_uuids, 26 | ) 27 | model = Model(epc_file = epc_file) 28 | rm_tree("tmp_dir") 29 | 30 | # Assert 31 | assert success is True 32 | assert index == input_index 33 | for prop_uuid in prop_uuids: 34 | mesh_uuid = model.uuid(obj_type = "Grid2dRepresentation", related_uuid = prop_uuid) 35 | assert mesh_uuid is not None 36 | mesh = Mesh(model, uuid = mesh_uuid) 37 | assert mesh is not None 38 | assert mesh.ni == grid.ni 39 | assert mesh.nj == grid.nj 40 | assert len(uuid_list) == 21 41 | -------------------------------------------------------------------------------- /tests/unit_tests/olio/test_dataframe.py: -------------------------------------------------------------------------------- 1 | # resqpy dataframe storage and retrieval test 2 | 3 | import os 4 | 5 | import numpy as np 6 | import pandas as pd 7 | 8 | import resqpy.model as rq 9 | import resqpy.olio.dataframe as rqdf 10 | import resqpy.time_series as rqts 11 | 12 | 13 | def test_dataframe(tmp_path): 14 | 15 | epc = os.path.join(tmp_path, 'dataframe.epc') 16 | 17 | model = rq.new_model(epc_file = epc) 18 | 19 | np_df = np.array([[0.0, 0.0, 0.0], [1000.0, 0.0, 0.0], [1900.0, 100.0, 0.0], [2500.0, 500.0, 800.0], 20 | [3200.0, 1500.0, 1600.0]]) 21 | df_cols = ['COP', 'CWP', 'CWI'] 22 | col_units = ['m3', 'ft3', 'm3'] 23 | ts_list = ['2021-04-22', '2021-04-23', '2021-04-24', '2021-04-25', '2021-04-26'] 24 | 25 | df = pd.DataFrame(np_df, columns = df_cols) 26 | 27 | vanilla = rqdf.DataFrame(model, df = df, title = 'vanilla') 28 | vanilla.write_hdf5_and_create_xml() 29 | 30 | ts = rqts.time_series_from_list(ts_list) 31 | ts.set_model(model) 32 | ts.create_xml() 33 | 34 | timetable = rqdf.TimeTable(model, df = df, title = 'time table', time_series = ts) 35 | timetable.write_hdf5_and_create_xml() 36 | 37 | propertied = rqdf.TimeTable(model, df = df, realization = 0, title = 'time table realisation', time_series = ts) 38 | propertied.write_hdf5_and_create_xml() 39 | 40 | united = rqdf.TimeTable(model, 41 | df = df, 42 | realization = 0, 43 | title = 'united realisation', 44 | uom_list = col_units, 45 | time_series = ts) 46 | united.write_hdf5_and_create_xml() 47 | 48 | model.store_epc() 49 | model.h5_release() 50 | del model 51 | 52 | model = rq.Model(epc) 53 | df_roots = model.roots(obj_type = 'Grid2dRepresentation', extra = {'dataframe': 'true'}) 54 | assert len(df_roots) == 4 55 | 56 | v_uuid = model.uuid(obj_type = 'Grid2dRepresentation', extra = {'dataframe': 'true'}, title = 'vanilla') 57 | dataframe = rqdf.DataFrame(model, uuid = v_uuid) 58 | assert dataframe.n_cols == 3 59 | assert dataframe.n_rows == 5 60 | assert all(dataframe.dataframe() == df) 61 | assert all(dataframe.dataframe().columns == df_cols) 62 | 63 | tt_uuid = model.uuid(obj_type = 'Grid2dRepresentation', extra = {'dataframe': 'true'}, title = 'time table') 64 | time_table = rqdf.TimeTable(model, uuid = tt_uuid) 65 | assert time_table.time_series().timestamps == ts_list 66 | assert all(time_table.dataframe() == df) 67 | 68 | u_uuid = model.uuid(obj_type = 'Grid2dRepresentation', extra = {'dataframe': 'true'}, title = 'united realisation') 69 | ur = rqdf.TimeTable(model, uuid = u_uuid) 70 | assert ur.uom_list == col_units 71 | assert ur.time_series().timestamps == ts_list 72 | 73 | # only dataframes with a realization number are stored as a property object 74 | assert len(model.parts(obj_type = 'ContinuousProperty')) == 2 75 | -------------------------------------------------------------------------------- /tests/unit_tests/olio/test_factors.py: -------------------------------------------------------------------------------- 1 | # tests functions in resqpy.olio.factors module 2 | 3 | import pytest 4 | 5 | import resqpy.olio.factors as f 6 | 7 | 8 | def test_factors(): 9 | assert f.factorize(20) == [2, 2, 5] 10 | assert f.factorize(29) == [29] 11 | assert f.all_factors_from_primes(f.factorize(12)) == [1, 2, 3, 4, 6, 12] 12 | f60 = f.all_factors(60) 13 | assert f60 == [1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60] 14 | f6 = f.all_factors(6) 15 | assert f6 == [1, 2, 3, 6] 16 | f.remove_subset(f60, f6) 17 | assert f60 == [4, 5, 10, 12, 15, 20, 30, 60] 18 | -------------------------------------------------------------------------------- /tests/unit_tests/olio/test_fine_coarse.py: -------------------------------------------------------------------------------- 1 | # tests for resqpy.olio.fine_coarse class and functions 2 | 3 | import numpy as np 4 | import pytest 5 | from numpy.testing import assert_array_almost_equal 6 | 7 | import resqpy.olio.fine_coarse as rqfc 8 | 9 | 10 | def test_fine_coarse(): 11 | 12 | fc1 = rqfc.FineCoarse((3, 4, 5), (3, 4, 5)) 13 | assert fc1 is not None 14 | fc1.set_all_ratios_constant() 15 | fc1.assert_valid() 16 | assert fc1.ratios((2, 2, 2)) == (1, 1, 1) 17 | 18 | fc2 = rqfc.FineCoarse((9, 16, 10), (3, 4, 5)) 19 | assert fc2 is not None 20 | fc2.set_all_ratios_constant() 21 | fc2.set_all_proportions_equal() 22 | fc2.assert_valid() 23 | assert fc2.ratios((2, 2, 2)) == (3, 4, 2) 24 | 25 | assert fc2.coarse_for_fine_kji0((5, 5, 5)) == (1, 1, 2) 26 | assert fc2.coarse_for_fine_axial(1, 13) == 3 27 | assert fc2.fine_base_for_coarse_axial(0, 1) == 3 28 | assert fc2.fine_base_for_coarse((2, 2, 2)) == (6, 8, 4) 29 | assert np.all(fc2.fine_box_for_coarse((1, 2, 3)) == np.array([[3, 8, 6], [5, 11, 7]], dtype = int)) 30 | 31 | assert_array_almost_equal(fc2.proportion(1, 1), (0.25, 0.25, 0.25, 0.25)) 32 | assert_array_almost_equal(fc2.interpolation(1, 1), (0.0, 0.25, 0.5, 0.75)) 33 | 34 | # todo: several more methods to test 35 | 36 | 37 | def test_fine_coarse_functions(): 38 | 39 | assert rqfc.axis_for_letter('k') == 0 40 | assert rqfc.axis_for_letter('J') == 1 41 | assert rqfc.axis_for_letter('i') == 2 42 | assert rqfc.letter_for_axis(0) == 'K' 43 | assert rqfc.letter_for_axis(2) == 'I' 44 | 45 | fct = rqfc.tartan_refinement(coarse_extent_kji = (5, 7, 7), 46 | coarse_fovea_box = np.array([[0, 2, 2], [1, 4, 4]], dtype = int), 47 | fovea_ratios_kji = (3, 3, 4), 48 | decay_rates_kji = (1, 1, 1), 49 | decay_mode = 'linear') 50 | assert fct is not None 51 | fct.assert_valid() 52 | 53 | assert fct.coarse_extent_kji == (5, 7, 7) 54 | assert fct.fine_extent_kji == (10, 15, 22) 55 | assert np.all(fct.coarse_for_fine_axial_vector(0) == (0, 0, 0, 1, 1, 1, 2, 2, 3, 4)) 56 | for const in fct.constant_ratios: 57 | assert const is None 58 | assert np.all(fct.vector_ratios[1] == (1, 2, 3, 3, 3, 2, 1)) 59 | assert np.all(fct.vector_ratios[2] == (2, 3, 4, 4, 4, 3, 2)) 60 | ep = np.array((1.0 / 3, 1.0 / 3, 1.0 / 3, 1.0 / 3, 1.0 / 3, 1.0 / 3, 0.5, 0.5, 1, 1), dtype = float) 61 | assert_array_almost_equal(fct.proportions_for_axis(0), ep) 62 | ep = np.array( 63 | (1.0, 0.5, 0.5, 1.0 / 3, 1.0 / 3, 1.0 / 3, 1.0 / 3, 1.0 / 3, 1.0 / 3, 1.0 / 3, 1.0 / 3, 1.0 / 3, 0.5, 0.5, 1.0), 64 | dtype = float) 65 | assert_array_almost_equal(fct.proportions_for_axis(1), ep) 66 | ep = np.array((0.5, 0.5, 1.0 / 3, 1.0 / 3, 1.0 / 3, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 67 | 0.25, 0.25, 1.0 / 3, 1.0 / 3, 1.0 / 3, 0.5, 0.5), 68 | dtype = float) 69 | assert_array_almost_equal(fct.proportions_for_axis(2), ep) 70 | # todo: add test of tartan refinement in exponential mode 71 | -------------------------------------------------------------------------------- /tests/unit_tests/olio/test_intersection.py: -------------------------------------------------------------------------------- 1 | # test intersection functions 2 | 3 | import math as maths 4 | import numpy as np 5 | from numpy.testing import assert_array_almost_equal 6 | 7 | import resqpy.olio.intersection as meet 8 | 9 | 10 | def test_line_triangle_intersect(): 11 | line_p = np.array((2.0, 0.0, 3.0)) 12 | line_v = np.array((0.0, 10.0, 0.0)) 13 | tp = np.array([(2.0, 5.0, 2.0), (2.0, 6.0, 4.0), (1.0, 5.7, 3.2)]) 14 | 15 | xyz = meet.line_triangle_intersect(line_p, line_v, tp, line_segment = True) 16 | assert xyz is not None 17 | expected = np.array((2.0, 5.5, 3.0)) 18 | assert_array_almost_equal(xyz, expected) 19 | 20 | line_p[0] += 0.01 21 | xyz = meet.line_triangle_intersect(line_p, line_v, tp, line_segment = True) 22 | assert xyz is None 23 | 24 | xyz = meet.line_triangle_intersect(line_p, line_v, tp, line_segment = True, t_tol = 0.1) 25 | assert xyz is not None 26 | 27 | 28 | def test_point_projected_to_line_2d(): 29 | l1, l2 = np.array((-1.0, -1.0)), np.array((9.9, 9.9)) 30 | for p in [(21431.98, -3145.3), (12.56, 12.56), (5.0, 6.0), (0.0, 2347856.0)]: 31 | x, y = meet.point_projected_to_line_2d(np.array(p), l1, l2) 32 | assert maths.isclose(x, y) 33 | p = (20.0, 0.0) 34 | assert_array_almost_equal(meet.point_projected_to_line_2d(np.array(p), l1, l2), (10.0, 10.0)) 35 | 36 | 37 | def test_point_snapped_to_line_segment_2d(): 38 | l1, l2 = np.array((-1.0, -1.0)), np.array((9.9, 9.9)) 39 | p = (-1.0, -1.0) 40 | assert_array_almost_equal(meet.point_snapped_to_line_segment_2d(np.array(p), l1, l2), (-1.0, -1.0)) 41 | p = (-2.0, -2.0) 42 | assert_array_almost_equal(meet.point_snapped_to_line_segment_2d(np.array(p), l1, l2), (-1.0, -1.0)) 43 | p = (-3425423.0, -1.0) 44 | assert_array_almost_equal(meet.point_snapped_to_line_segment_2d(np.array(p), l1, l2), (-1.0, -1.0)) 45 | p = (-2.0, -5.0) 46 | assert_array_almost_equal(meet.point_snapped_to_line_segment_2d(np.array(p), l1, l2), (-1.0, -1.0)) 47 | p = (10.0, 10.0) 48 | assert_array_almost_equal(meet.point_snapped_to_line_segment_2d(np.array(p), l1, l2), (9.9, 9.9)) 49 | p = (20.0, 0.0) 50 | assert_array_almost_equal(meet.point_snapped_to_line_segment_2d(np.array(p), l1, l2), (9.9, 9.9)) 51 | p = (10.0, 0.0) 52 | assert_array_almost_equal(meet.point_snapped_to_line_segment_2d(np.array(p), l1, l2), (5.0, 5.0)) 53 | -------------------------------------------------------------------------------- /tests/unit_tests/olio/test_point_inclusion.py: -------------------------------------------------------------------------------- 1 | # test point in polygon functions 2 | 3 | import os 4 | 5 | # import math as maths 6 | import numpy as np 7 | 8 | import resqpy.olio.point_inclusion as pip 9 | 10 | # from numpy.testing import assert_array_almost_equal 11 | 12 | 13 | def test_pip_cn_and_wn(): 14 | # unit square polygon 15 | poly = np.array([(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)]) 16 | p_in = np.array([(0.00001, 0.00001), (0.00001, 0.99999), (0.99999, 0.00001), (0.99999, 0.99999)]) 17 | p_out = np.array([(1.1, 0.1), (-0.1, 0.2), (0.5, 1.00001), (0.4, -0.0001), (1.00001, 1.00001), (1.00001, -0.00001)]) 18 | for pip_fn in [pip.pip_cn, pip.pip_wn]: 19 | assert pip_fn((0.5, 0.5), poly) 20 | for p in p_in: 21 | assert pip_fn(p, poly) 22 | for p in p_out: 23 | assert not pip_fn(p, poly) 24 | assert np.all(pip.pip_array_cn(p_in, poly)) 25 | assert not np.any(pip.pip_array_cn(p_out, poly)) 26 | 27 | 28 | def test_figure_of_eight(): 29 | fig_8 = np.array([(-100.0, -200.0), (100.0, 200.0), (-100.0, 200.0), (100.0, -200.0)]) 30 | p_in = np.array([(-99.0, -199.0), (0.0, -1.0), (99.0, 199.0), (0.0, 1.0), (49.9, -100.0)]) 31 | p_out = np.array([(1000.0, -23.0), (1.0, 0.0), (-0.001, 0.0), (-50.1, 100.0)]) 32 | for pip_fn in (pip.pip_cn, pip.pip_wn): 33 | for p in p_in: 34 | assert pip_fn(p, fig_8) 35 | for p in p_out: 36 | assert not pip_fn(p, fig_8) 37 | assert np.all(pip.pip_array_cn(p_in, fig_8)) 38 | assert not np.any(pip.pip_array_cn(p_out, fig_8)) 39 | 40 | 41 | def test_points_in_polygon(tmp_path): 42 | # create an ascii file holding vertices of a polygon 43 | poly_file = os.path.join(tmp_path, 'diamond.txt') 44 | diamond = np.array([(0.0, 3.0, 0.0), (3.0, 6.0, -1.3), (6.0, 3.0, 12.5), (3.0, 0.0, 0.0)]) 45 | with open(poly_file, 'w') as fp: 46 | for xyz in diamond: 47 | fp.write(f'{xyz[0]} {xyz[1]} {xyz[2]}\n') 48 | fp.write('999.0 999.0 999.0\n') 49 | # test some points with no multiplier applied to polygon geometry 50 | p_in = np.array([(3.0, 3.0), (0.1, 3.0), (1.55, 1.55), (4.49, 4.49), (5.99, 3.0), (3.1, 0.11)]) 51 | p_out = np.array([(-3.0, -3.0), (2.0, 0.99), (4.51, 4.51), (6.01, 3.0)]) 52 | assert np.all(pip.points_in_polygon(p_in[:, 0], p_in[:, 1], poly_file)) 53 | assert not np.any(pip.points_in_polygon(p_out[:, 0], p_out[:, 1], poly_file)) 54 | # test some points with multiplier applied to polygon geometry 55 | multiplier = 2.0 / 3.0 56 | p_in *= multiplier 57 | p_out *= multiplier 58 | assert np.all(pip.points_in_polygon(p_in[:, 0], p_in[:, 1], poly_file, poly_unit_multiplier = multiplier)) 59 | assert not np.any(pip.points_in_polygon(p_out[:, 0], p_out[:, 1], poly_file, poly_unit_multiplier = multiplier)) 60 | 61 | 62 | def test_scan(): 63 | kite = np.array([(0.1, 3.0, 0.0), (3.0, 4.9, 0.0), (5.9, 3.0, 0.0), (3.0, 0.1, 0.0)]) 64 | origin = (-1.0, 0.0) 65 | ncol = 8 66 | nrow = 6 67 | expected = np.array([(0, 0, 0, 0, 0, 0, 0, 0), (0, 0, 0, 0, 1, 0, 0, 0), (0, 0, 0, 1, 1, 1, 0, 0), 68 | (0, 0, 1, 1, 1, 1, 1, 0), (0, 0, 0, 1, 1, 1, 0, 0), (0, 0, 0, 0, 0, 0, 0, 0)], 69 | dtype = bool) 70 | assert np.all(pip.scan(origin, ncol, nrow, 1.0, 1.0, kite) == expected) 71 | -------------------------------------------------------------------------------- /tests/unit_tests/olio/test_read_nexus_fault.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import pytest 3 | from resqpy.olio.read_nexus_fault import load_nexus_fault_mult_table 4 | 5 | 6 | @pytest.mark.parametrize("file_contents, expected_fault_definition", [("""MULT TX ALL PLUS MULT 7 | FNAME BLUE 8 | 12 3 56 56 1 60 1.0000 9 | 14 15 55 55 1 60 0.0000 10 | 11 | MULT TY ALL MINUS MULT 12 | FNAME RED 13 | 20 27 55 55 1 60 1.0000 14 | 28 37 54 54 1 60 1.0000 15 | """, 16 | pd.DataFrame({ 17 | 'i1': [12, 14, 20, 28], 18 | 'i2': [3, 15, 27, 37], 19 | 'j1': [56, 55, 55, 54], 20 | 'j2': [56, 55, 55, 54], 21 | 'k1': [1, 1, 1, 1], 22 | 'k2': [60, 60, 60, 60], 23 | 'mult': [1.0, 0.0, 1.0, 1.0], 24 | 'grid': ['ROOT', 'ROOT', 'ROOT', 'ROOT'], 25 | 'name': ['BLUE', 'BLUE', 'RED', 'RED'], 26 | 'face': ['I', 'I', 'J-', 'J-'] 27 | }))], 28 | ids = ['basic']) 29 | def test_load_nexus_fault_mult_table(mocker, file_contents, expected_fault_definition): 30 | # Arrange 31 | # mock out open to return test file contents 32 | open_mock = mocker.mock_open(read_data = file_contents) 33 | mocker.patch("builtins.open", open_mock) 34 | 35 | # Act 36 | fault_mult_table = load_nexus_fault_mult_table('test/fault/file.inc') 37 | 38 | # Assert 39 | pd.testing.assert_frame_equal(fault_mult_table, expected_fault_definition) -------------------------------------------------------------------------------- /tests/unit_tests/olio/test_transmission.py: -------------------------------------------------------------------------------- 1 | # test module for resqpy.olio.transmission.py 2 | 3 | import math as maths 4 | import os 5 | 6 | import numpy as np 7 | import pytest 8 | from numpy.testing import assert_array_almost_equal 9 | 10 | import resqpy.grid as grr 11 | import resqpy.model as rq 12 | import resqpy.olio.transmission as rqtr 13 | import resqpy.olio.uuid as bu 14 | import resqpy.olio.vector_utilities as vec 15 | 16 | 17 | def test_regular_grid_half_cell_transmission(tmp_path): 18 | 19 | def try_one_half_t_regular(model, 20 | extent_kji = (2, 2, 2), 21 | dxyz = (1.0, 1.0, 1.0), 22 | perm_kji = (1.0, 1.0, 1.0), 23 | ntg = 1.0, 24 | darcy_constant = 1.0, 25 | rotate = None, 26 | dip = None): 27 | ones = np.ones(extent_kji) 28 | grid = grr.RegularGrid(model, extent_kji = extent_kji, dxyz = dxyz) 29 | if dip is not None: # dip positive x axis downwards 30 | r_matrix = vec.rotation_matrix_3d_axial(1, dip) 31 | p = grid.points_ref(masked = False) 32 | p[:] = vec.rotate_array(r_matrix, p) 33 | if rotate is not None: # rotate anticlockwise in xy plane (viewed from above) 34 | r_matrix = vec.rotation_matrix_3d_axial(2, rotate) 35 | p = grid.points_ref(masked = False) 36 | p[:] = vec.rotate_array(r_matrix, p) 37 | half_t = rqtr.half_cell_t(grid, 38 | perm_k = perm_kji[0] * ones, 39 | perm_j = perm_kji[1] * ones, 40 | perm_i = perm_kji[2] * ones, 41 | ntg = ntg * ones, 42 | darcy_constant = darcy_constant) 43 | expected = 2.0 * darcy_constant * np.array( 44 | (perm_kji[0] * dxyz[0] * dxyz[1] / dxyz[2], ntg * perm_kji[1] * dxyz[0] * dxyz[2] / dxyz[1], 45 | ntg * perm_kji[2] * dxyz[1] * dxyz[2] / dxyz[0])) 46 | assert np.all(np.isclose(half_t, expected.reshape(1, 1, 1, 3))) 47 | 48 | temp_epc = str(os.path.join(tmp_path, f"{bu.new_uuid()}.epc")) 49 | model = rq.Model(temp_epc, new_epc = True, create_basics = True) 50 | 51 | try_one_half_t_regular(model) 52 | try_one_half_t_regular(model, extent_kji = (3, 4, 5)) 53 | try_one_half_t_regular(model, dxyz = (127.53, 21.05, 12.6452)) 54 | try_one_half_t_regular(model, perm_kji = (123.23, 512.4, 314.7)) 55 | try_one_half_t_regular(model, ntg = 0.7) 56 | try_one_half_t_regular(model, darcy_constant = 0.001127) 57 | try_one_half_t_regular(model, 58 | extent_kji = (5, 4, 3), 59 | dxyz = (84.23, 77.31, 15.823), 60 | perm_kji = (0.6732, 298.14, 384.2), 61 | ntg = 0.32, 62 | darcy_constant = 0.008527) 63 | try_one_half_t_regular(model, rotate = 67.8) 64 | 65 | # should not have written anything, but try clean-up just in case 66 | try: 67 | os.remove(temp_epc) 68 | except Exception: 69 | pass 70 | try: 71 | os.remove(temp_epc[-4] + '.h5') 72 | except Exception: 73 | pass 74 | 75 | 76 | def test_half_cell_t_2d_triangular_precursor_equilateral(): 77 | side = 123.45 78 | height = side * maths.sin(vec.radians_from_degrees(60.0)) 79 | # create a pair of equilateral triangles 80 | p = np.array([(0.0, 0.0), (side, 0.0), (side / 2.0, height), (side * 3.0 / 2.0, height)]) 81 | t = np.array([(0, 1, 2), (1, 2, 3)], dtype = int) 82 | a, b = rqtr.half_cell_t_2d_triangular_precursor(p, t) 83 | a_over_l = a / b 84 | expected = side / (height / 3) 85 | assert a_over_l.shape == (2, 3) 86 | assert_array_almost_equal(a_over_l, expected) 87 | -------------------------------------------------------------------------------- /tests/unit_tests/olio/test_uuid.py: -------------------------------------------------------------------------------- 1 | # test functions for olio.uuid module 2 | 3 | import resqpy.olio.uuid as bu 4 | 5 | 6 | def test_new_uuid(): 7 | u1 = bu.new_uuid() 8 | u2 = bu.new_uuid() 9 | assert bu.is_uuid(u1) and bu.is_uuid(u2) 10 | assert not bu.matching_uuids(u1, u2) 11 | 12 | 13 | def test_test_mode(): 14 | # note: test failure between on and off calls is likely to result in later tests failing 15 | u_a = bu.new_uuid() 16 | bu.switch_on_test_mode(seed = 11) 17 | u12 = bu.new_uuid() 18 | u13 = bu.new_uuid() 19 | bu.switch_off_test_mode() 20 | u_b = bu.new_uuid() 21 | assert u_a.int != 11 22 | assert u12.int == 12 23 | assert u13.int == 13 24 | assert u_b.int != 14 25 | 26 | 27 | def test_string_to_from_uuid(): 28 | u = bu.new_uuid() 29 | s = bu.string_from_uuid(u) 30 | u2 = bu.uuid_from_string(s) 31 | assert isinstance(s, str) 32 | assert len(s) == 36 33 | assert all([s[i] == '-' for i in (8, 13, 18, 23)]) 34 | assert all([ch in '0123456789abcdef-' for ch in s]) 35 | assert bu.is_uuid(s) 36 | assert bu.matching_uuids(s, u) 37 | assert bu.matching_uuids(u, s) 38 | assert bu.is_uuid(u2) 39 | assert u2.int == u.int 40 | assert bu.matching_uuids(u2, u) 41 | assert u2 == u 42 | 43 | 44 | def test_int_to_from_uuid(): 45 | u = bu.new_uuid() 46 | i = bu.uuid_as_int(u) 47 | u2 = bu.uuid_from_int(i) 48 | assert isinstance(i, int) 49 | assert i > 0 50 | assert u2 == u 51 | assert i == u.int 52 | 53 | 54 | def test_uuid_as_bytes(): 55 | ub = bu.uuid_as_bytes(bu.new_uuid()) 56 | assert isinstance(ub, bytes) 57 | assert len(ub) == 16 58 | 59 | 60 | def test_uuid_in_list(): 61 | u = bu.new_uuid() 62 | u1 = bu.new_uuid() 63 | u2 = bu.new_uuid() 64 | u3 = bu.new_uuid() 65 | not_list = [u1, u2, u3] 66 | u_list = [u1, u2, u, u3] 67 | s_list = (str(u1), str(u3), str(u)) 68 | us = str(u) 69 | ui = u.int 70 | assert not bu.uuid_in_list(u, not_list) 71 | assert not bu.uuid_in_list(us, not_list) 72 | for uuid_list in [u_list, s_list]: 73 | for uuid in (u, us, ui): 74 | assert bu.uuid_in_list(uuid, uuid_list) 75 | -------------------------------------------------------------------------------- /tests/unit_tests/olio/test_vdb.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import resqpy.olio.vdb as vdb 4 | import numpy as np 5 | 6 | 7 | @pytest.mark.parametrize("byte_array, expected_array", [(b"\x20\x01\x00\x00\xae\x01\x00\x00", np.array([288, 430])), 8 | (b"\xca\x02\x00\x00\xe8\x02\x00\x00", np.array([714, 744]))]) 9 | def test_raw_data_class_item_type_P(mocker, tmp_path, byte_array, expected_array): 10 | # Arrange 11 | place = 0 12 | item_type = 'P' 13 | count = 2 14 | max_count = 20 15 | 16 | open_mock = mocker.mock_open(read_data = byte_array) 17 | mocker.patch("builtins.open", open_mock) 18 | 19 | # Act 20 | with open(tmp_path) as fp: 21 | raw_data = vdb.RawData(fp, place, item_type, count, max_count) 22 | 23 | # Assert 24 | np.testing.assert_array_almost_equal(raw_data.a, expected_array) 25 | -------------------------------------------------------------------------------- /tests/unit_tests/olio/test_xml_et.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import math as maths 3 | 4 | import resqpy.well as rqw 5 | import resqpy.olio.xml_et as rqet 6 | 7 | 8 | def test_list_obj_references(example_model_with_well): 9 | 10 | model, well_interp, datum, traj = example_model_with_well 11 | 12 | i_refs = rqet.list_obj_references(well_interp.root) 13 | d_refs = rqet.list_obj_references(datum.root) 14 | t_refs = rqet.list_obj_references(traj.root) 15 | t_refs_with_hdf5 = rqet.list_obj_references(traj.root, skip_hdf5 = False) 16 | 17 | assert len(i_refs) == 1 # feature reference 18 | assert len(d_refs) == 1 # crs reference 19 | assert len(t_refs) == 3 # interp, crs & datum references 20 | assert len(t_refs_with_hdf5) == 5 # as above, plus mds and control points hdf5 references 21 | 22 | 23 | def test_print_xml_tree(example_model_with_well): 24 | # note: this test only does a rudimentary check of the number of lines reported 25 | 26 | model, well_interp, datum, traj = example_model_with_well 27 | 28 | lines = rqet.print_xml_tree(datum.root, 29 | level = 0, 30 | max_level = None, 31 | strip_tag_refs = True, 32 | to_log = False, 33 | log_level = None, 34 | max_lines = 0, 35 | line_count = 0) 36 | assert lines == 15 37 | lines = rqet.print_xml_tree(well_interp.root, 38 | level = 0, 39 | max_level = 10, 40 | strip_tag_refs = True, 41 | to_log = False, 42 | log_level = None, 43 | max_lines = 0, 44 | line_count = 0) 45 | assert lines == 12 46 | lines = rqet.print_xml_tree(traj.root, 47 | level = 0, 48 | max_level = 4, 49 | strip_tag_refs = False, 50 | to_log = True, 51 | log_level = 'info', 52 | max_lines = 20, 53 | line_count = 0) 54 | assert lines == 21 55 | 56 | 57 | def test_miscellaneous_xml_et_functions(example_model_with_well): 58 | 59 | model, well_interp, datum, traj = example_model_with_well 60 | 61 | assert rqet.list_of_tag(None, 'Name') is None 62 | assert rqet.list_of_descendant_tag(None, 'Name') is None 63 | assert rqet.list_obj_references(None) == [] 64 | assert rqet.count_tag(None, 'ExtraMetadata') is None 65 | assert rqet.find_metadata_item_node_in_xml(None, 'source') is None 66 | 67 | datetime_str = rqet.find_nested_tags_cast(well_interp.root, ['Citation', 'Creation'], dtype = str) 68 | assert isinstance(datetime_str, str) 69 | assert len(datetime_str) == 20 70 | assert datetime_str.startswith('20') 71 | assert datetime_str.endswith('Z') 72 | 73 | assert datum.extra_metadata == {} 74 | model.set_source_for_part(datum.part, 'true') 75 | datum = rqw.MdDatum(model, uuid = datum.uuid) 76 | assert len(datum.extra_metadata) == 1 77 | emv = rqet.find_nested_tags_bool(datum.root, ['ExtraMetadata', 'Value']) 78 | assert emv is not None 79 | assert isinstance(emv, bool) 80 | assert emv is True 81 | model.set_source_for_part(datum.part, '3.14') 82 | datum = rqw.MdDatum(model, uuid = datum.uuid) 83 | assert len(datum.extra_metadata) == 1 84 | emv = rqet.find_nested_tags_float(datum.root, ['ExtraMetadata', 'Value']) 85 | assert emv is not None 86 | assert isinstance(emv, float) 87 | assert maths.isclose(emv, 3.14) 88 | -------------------------------------------------------------------------------- /tests/unit_tests/test_resqpy.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from packaging.version import Version 3 | 4 | import resqpy 5 | from resqpy import model 6 | 7 | 8 | def test_empty_model(): 9 | _ = model.Model() 10 | return 11 | 12 | 13 | def test_all_imports(): 14 | from resqpy import (crs, derived_model, fault, grid, grid_surface, lines, organize, property, rq_import, surface, 15 | time_series, well) 16 | 17 | # The line below prevents IDEs from deleting the above 18 | _ = (crs, derived_model, fault, grid, grid_surface, lines, organize, property, rq_import, surface, time_series, 19 | well) 20 | 21 | # from resqpy.olio import * 22 | return 23 | 24 | 25 | @pytest.mark.buildtest 26 | def test_version(): 27 | # This is dynamically created when package is built 28 | version_string = resqpy.__version__ 29 | 30 | # Ensure version string is a PEP-440 compliant version 31 | version = Version(version_string) 32 | assert version >= Version("1.1.1") 33 | -------------------------------------------------------------------------------- /tests/unit_tests/time_series/test_time_duration.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import datetime as dt 3 | 4 | from resqpy.time_series import TimeDuration 5 | 6 | 7 | @pytest.mark.parametrize('days, hours, earlier_timestamp, later_timestamp', 8 | [(1, 12, '2021-12-13T00:00:00Z', '2021-12-14T12:00:00Z'), 9 | (30, 0, '2021-12-13T00:00:00Z', '2022-01-12T00:00:00Z'), 10 | (30, 23, '2022-04-13T06:30:00Z', '2022-05-14T05:30:00Z'), 11 | (365, 0, '2024-01-01T00:00:00Z', '2024-12-31T00:00:00Z'), 12 | (0, 0, '2014-01-01T00:00:00Z', '2014-01-01T00:00:00Z')]) 13 | def test_timestamp_before_and_after_duration(days, hours, earlier_timestamp, later_timestamp): 14 | duration = TimeDuration(days = days, hours = hours) 15 | assert duration.timestamp_before_duration(later_timestamp) == earlier_timestamp 16 | assert duration.timestamp_after_duration(earlier_timestamp) == later_timestamp 17 | -------------------------------------------------------------------------------- /tests/unit_tests/well/test_md_datum.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import resqpy.well 4 | from resqpy.model import Model 5 | 6 | 7 | def test_MdDatum(example_model_and_crs): 8 | 9 | # Set up a new datum 10 | model, crs = example_model_and_crs 11 | epc = model.epc_file 12 | data = dict( 13 | location = (0, -99999, 3.14), 14 | md_reference = 'mean low water', 15 | ) 16 | datum = resqpy.well.MdDatum(parent_model = model, crs_uuid = crs.uuid, **data) 17 | uuid = datum.uuid 18 | 19 | # Save to disk and reload 20 | datum.create_xml() 21 | model.store_epc() 22 | 23 | del model, crs, datum 24 | model2 = Model(epc_file = epc) 25 | datum2 = resqpy.well.MdDatum(parent_model = model2, uuid = uuid) 26 | 27 | for key, expected_value in data.items(): 28 | assert getattr(datum2, key) == expected_value, f"Issue with {key}" 29 | 30 | identical = resqpy.well.MdDatum(parent_model = model2, crs_uuid = datum2.crs_uuid, **data) 31 | data['md_reference'] = 'kelly bushing' 32 | different = resqpy.well.MdDatum(parent_model = model2, crs_uuid = datum2.crs_uuid, **data) 33 | assert identical == datum2 34 | assert different != datum2 35 | 36 | 37 | @pytest.mark.parametrize('other_data,expected', 38 | [(dict(location = (0, -99999, 3.14), md_reference = 'mean low water'), True), 39 | (dict(location = (5, -30000, 5), md_reference = 'kelly bushing'), False)]) 40 | def test_is_equivalent(example_model_and_crs, other_data, expected): 41 | # Test that comparison of metadata items returns the expected result 42 | 43 | # --------- Arrange ---------- 44 | model, crs = example_model_and_crs 45 | data = dict(location = (0, -99999, 3.14), md_reference = 'mean low water') 46 | 47 | # --------- Act ---------- 48 | datum1 = resqpy.well.MdDatum(parent_model = model, crs_uuid = crs.uuid, **data) 49 | datum2 = resqpy.well.MdDatum(parent_model = model, crs_uuid = crs.uuid, **other_data) 50 | result = datum1.is_equivalent(other = datum2) 51 | 52 | # --------- Assert ---------- 53 | assert result is expected 54 | -------------------------------------------------------------------------------- /tests/unit_tests/well/test_well.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | from numpy.testing import assert_array_almost_equal 4 | from pandas.testing import assert_frame_equal 5 | 6 | 7 | def test_trajectory_iterators(example_model_with_well): 8 | # Test that trajectories can be found via iterators 9 | 10 | model, well_interp, datum, traj = example_model_with_well 11 | 12 | # Iterate via wells 13 | uuids_1 = [] 14 | for well in model.iter_wellbore_interpretations(): 15 | for traj in well.iter_trajectories(): 16 | uuids_1.append(traj.uuid) 17 | 18 | # Iterate directly 19 | uuids_2 = [] 20 | for traj in model.iter_trajectories(): 21 | uuids_2.append(traj.uuid) 22 | 23 | # Two sets of trajectories should match, assuming all trajectories have a parent well 24 | assert len(uuids_1) > 0 25 | assert sorted(uuids_1) == sorted(uuids_2) 26 | 27 | 28 | def test_logs(example_model_with_logs): 29 | 30 | model, well_interp, datum, traj, frame, log_collection = example_model_with_logs 31 | 32 | # Check logs can be extracted from resqml dataset 33 | 34 | # Check exactly one wellbore frame exists in the model 35 | discovered_frames = 0 36 | for trajectory in model.iter_trajectories(): 37 | for frame in trajectory.iter_wellbore_frames(): 38 | discovered_frames += 1 39 | assert discovered_frames == 1 40 | 41 | # Check MDs 42 | mds = frame.node_mds 43 | assert isinstance(mds, np.ndarray) 44 | assert_array_almost_equal(mds, [1, 2, 3, 4]) 45 | 46 | # Check logs 47 | log_list = list(log_collection.iter_logs()) 48 | assert len(log_list) == 2 49 | 50 | # TODO: would be nice to write: log_collection.get_curve("GR") 51 | gr = log_list[0] 52 | assert gr.title == "GR" 53 | assert gr.uom == "gAPI" 54 | assert_array_almost_equal(gr.values(), [1, 2, 1, 2]) 55 | 56 | nphi = log_list[1] 57 | assert nphi.title == 'NPHI' 58 | 59 | # TODO: get more units working 60 | # assert nphi.uom == "v/v" 61 | assert_array_almost_equal(nphi.values(), [0.1, 0.1, np.nan, np.nan]) 62 | 63 | 64 | def test_logs_conversion(example_model_with_logs): 65 | 66 | model, well_interp, datum, traj, frame, log_collection = example_model_with_logs 67 | 68 | # Pandas 69 | df = log_collection.to_df() 70 | df_expected = pd.DataFrame(data = {"GR": [1, 2, 1, 2], "NPHI": [0.1, 0.1, np.nan, np.nan]}, index = [1, 2, 3, 4]) 71 | assert_frame_equal(df_expected, df, check_dtype = False) 72 | 73 | # LAS 74 | las = log_collection.to_las() 75 | assert las.well.WELL.value == 'well A' 76 | 77 | gr = las.get_curve("GR") 78 | assert gr.unit.casefold() == "GAPI".casefold() 79 | assert_array_almost_equal(gr.data, [1, 2, 1, 2]) 80 | 81 | nphi = las.get_curve("NPHI") 82 | # assert nphi.unit == "GAPI" 83 | assert_array_almost_equal(nphi.data, [0.1, 0.1, np.nan, np.nan]) 84 | --------------------------------------------------------------------------------