├── .coveragerc ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── release.yml └── workflows │ ├── bump-version-dev.yml │ ├── bump-version.yml │ ├── cleanup-tags.yml │ ├── examples.yml │ ├── gh-pages.yml │ ├── logger.sh │ ├── nightly.yml │ ├── publish-to-pypi.yml │ ├── release-notes.yml │ ├── style-check.yml │ ├── tests.yml │ ├── utils.sh │ └── verify-pip-installation.yml ├── .gitignore ├── CITATION.cff ├── CODE_OF_CONDUCT.md ├── DEV_GUIDE.md ├── LICENSE ├── Makefile ├── README.md ├── VERSIONING.md ├── codecov.yml ├── docs ├── Makefile ├── _examples │ ├── applications.rst │ ├── index.rst │ ├── reference.rst │ └── tutorials.rst ├── _static │ ├── css │ │ └── custom.css │ ├── images │ │ ├── conc_dist.png │ │ ├── droplet.png │ │ ├── extracted_berea.png │ │ ├── find_neighbor_pores_three_labels.png │ │ ├── find_neighbor_pores_three_labels_version_three.png │ │ ├── generic_geometry_histogram.png │ │ ├── histogram.png │ │ ├── openpnm_logo.jpg │ │ ├── percolation.png │ │ ├── quick_start_drainage_curve.png │ │ ├── reactive_diffusion.png │ │ ├── rel_diff.png │ │ ├── stick_and_ball_histogram.png │ │ └── transient.png │ └── js │ │ └── custom.js ├── _templates │ └── autosummary │ │ ├── base.rst │ │ ├── class.rst │ │ ├── method.rst │ │ └── module.rst ├── conf.py ├── index.rst ├── installation.rst ├── make.bat └── modules │ └── index.rst ├── example.py ├── examples ├── applications │ ├── Sandstone.csv │ ├── absolute_permeability.ipynb │ ├── adjusting_pore_size_distributions.ipynb │ ├── dispersion_coefficient.ipynb │ ├── effective_diffusivity_and_tortuosity.ipynb │ ├── formation_factor.ipynb │ ├── mercury_intrusion.ipynb │ ├── network_extraction.ipynb │ ├── porosity.ipynb │ ├── relative_diffusivity.ipynb │ └── relative_permeability.ipynb ├── contrib │ └── README.rst ├── getting_started.ipynb ├── reference │ ├── architecture │ │ ├── pore_scale_models.ipynb │ │ ├── settings_attribute_machinery.ipynb │ │ └── workspace_and_projects.ipynb │ ├── networks │ │ └── managing_clustered_networks.ipynb │ └── simulations │ │ ├── available_matrix_solvers.ipynb │ │ ├── basic_transport.ipynb │ │ ├── explanation_of_units.ipynb │ │ ├── reactive_transport.ipynb │ │ ├── size_factors_and_transport_conductance.ipynb │ │ └── transient_transport.ipynb └── tutorials │ ├── 01_numerical_python_primer.ipynb │ ├── 02_network_generation_and_visualization.ipynb │ ├── 03_data_and_topology_storage.ipynb │ ├── 04_graph_queries_and_manipulation.ipynb │ ├── 05_defining_geometry.ipynb │ ├── 06_labels_and_domains.ipynb │ ├── 07_phases_and_mixtures.ipynb │ ├── 08_simulating_transport.ipynb │ ├── 09_simulating_invasion.ipynb │ └── 10_visualization_options.ipynb ├── openpnm ├── __init__.py ├── __version__.py ├── _skgraph │ ├── __init__.py │ ├── generators │ │ ├── __init__.py │ │ ├── _bcc.py │ │ ├── _cubic.py │ │ ├── _delaunay.py │ │ ├── _fcc.py │ │ ├── _gabriel.py │ │ ├── _template.py │ │ ├── _voronoi.py │ │ ├── _voronoi_delaunay_dual.py │ │ ├── network │ │ └── tools │ │ │ ├── __init__.py │ │ │ └── _funcs.py │ ├── io │ │ ├── __init__.py │ │ └── _funcs.py │ ├── operations │ │ ├── __init__.py │ │ ├── _binary.py │ │ ├── _funcs.py │ │ └── _unary.py │ ├── queries │ │ ├── QuickUnion.py │ │ ├── __init__.py │ │ ├── _funcs.py │ │ └── _qupc.py │ ├── simulations │ │ ├── __init__.py │ │ ├── _funcs.py │ │ └── _percolation.py │ ├── tools │ │ ├── GraphBundle.py │ │ ├── __init__.py │ │ ├── _coords_transforms.py │ │ └── _funcs.py │ └── visualization │ │ ├── __init__.py │ │ └── _funcs.py ├── algorithms │ ├── __init__.py │ ├── _advection_diffusion.py │ ├── _algorithm.py │ ├── _drainage.py │ ├── _fickian_diffusion.py │ ├── _fourier_conduction.py │ ├── _invasion_percolation.py │ ├── _ohmic_conduction.py │ ├── _reactive_transport.py │ ├── _solution.py │ ├── _stokes_flow.py │ ├── _transient_advection_diffusion.py │ ├── _transient_fickian_diffusion.py │ ├── _transient_fourier_conduction.py │ ├── _transient_reactive_transport.py │ └── _transport.py ├── beta │ ├── __init__.py │ └── _funcs.py ├── contrib │ ├── __init__.py │ ├── _multiphase.py │ └── _transient_multiphysics.py ├── core │ ├── __init__.py │ ├── _base2.py │ ├── _mixins.py │ └── _models.py ├── integrators │ ├── __init__.py │ ├── _base.py │ └── _scipy.py ├── io │ ├── __init__.py │ ├── _comsol.py │ ├── _csv.py │ ├── _dict.py │ ├── _hdf5.py │ ├── _jsongraph.py │ ├── _marock.py │ ├── _networkx.py │ ├── _pandas.py │ ├── _paraview.py │ ├── _pergeos.py │ ├── _porespy.py │ ├── _salome.py │ ├── _statoil.py │ ├── _stl.py │ ├── _utils.py │ ├── _vtk.py │ └── _xdmf.py ├── models │ ├── __init__.py │ ├── _doctxt.py │ ├── collections │ │ ├── __init__.py │ │ ├── geometry │ │ │ ├── __init__.py │ │ │ ├── circles_and_rectangles.py │ │ │ ├── cones_and_cylinders.py │ │ │ ├── cubes_and_cuboids.py │ │ │ ├── pyramids_and_cuboids.py │ │ │ ├── spheres_and_cylinders.py │ │ │ ├── squares_and_rectangles.py │ │ │ └── trapezoids_and_rectangles.py │ │ ├── network │ │ │ └── __init__.py │ │ ├── phase │ │ │ ├── __init__.py │ │ │ ├── air.py │ │ │ ├── gas.py │ │ │ ├── liquid.py │ │ │ ├── mercury.py │ │ │ └── water.py │ │ └── physics │ │ │ ├── __init__.py │ │ │ ├── basic.py │ │ │ └── standard.py │ ├── geometry │ │ ├── __init__.py │ │ ├── _geodocs.py │ │ ├── conduit_lengths │ │ │ ├── __init__.py │ │ │ └── _funcs.py │ │ ├── diffusive_size_factors │ │ │ ├── __init__.py │ │ │ └── _funcs.py │ │ ├── hydraulic_size_factors │ │ │ ├── __init__.py │ │ │ └── _funcs.py │ │ ├── pore_cross_sectional_area │ │ │ ├── __init__.py │ │ │ └── _funcs.py │ │ ├── pore_seed │ │ │ ├── __init__.py │ │ │ └── _funcs.py │ │ ├── pore_size │ │ │ ├── __init__.py │ │ │ └── _funcs.py │ │ ├── pore_surface_area │ │ │ ├── __init__.py │ │ │ └── _funcs.py │ │ ├── pore_volume │ │ │ ├── __init__.py │ │ │ └── _funcs.py │ │ ├── throat_capillary_shape_factor │ │ │ ├── __init__.py │ │ │ └── _funcs.py │ │ ├── throat_centroid │ │ │ ├── __init__.py │ │ │ └── _funcs.py │ │ ├── throat_cross_sectional_area │ │ │ ├── __init__.py │ │ │ └── _funcs.py │ │ ├── throat_endpoints │ │ │ ├── __init__.py │ │ │ └── _funcs.py │ │ ├── throat_length │ │ │ ├── __init__.py │ │ │ └── _funcs.py │ │ ├── throat_perimeter │ │ │ ├── __init__.py │ │ │ └── _funcs.py │ │ ├── throat_seed │ │ │ ├── __init__.py │ │ │ └── _funcs.py │ │ ├── throat_size │ │ │ ├── __init__.py │ │ │ └── _funcs.py │ │ ├── throat_surface_area │ │ │ ├── __init__.py │ │ │ └── _funcs.py │ │ ├── throat_vector │ │ │ ├── __init__.py │ │ │ └── _funcs.py │ │ └── throat_volume │ │ │ ├── __init__.py │ │ │ └── _funcs.py │ ├── misc │ │ ├── __init__.py │ │ ├── _basic_math.py │ │ ├── _neighbor_lookups.py │ │ ├── _simple_equations.py │ │ └── _statistical_distributions.py │ ├── network │ │ ├── __init__.py │ │ ├── _health.py │ │ └── _topology.py │ ├── phase │ │ ├── __init__.py │ │ ├── _phasedocs.py │ │ ├── critical_props │ │ │ ├── __init__.py │ │ │ └── _funcs.py │ │ ├── density │ │ │ ├── __init__.py │ │ │ └── _funcs.py │ │ ├── diffusivity │ │ │ ├── __init__.py │ │ │ └── _funcs.py │ │ ├── heat_capacity │ │ │ ├── __init__.py │ │ │ └── _funcs.py │ │ ├── misc │ │ │ ├── __init__.py │ │ │ └── _funcs.py │ │ ├── mixtures │ │ │ ├── __init__.py │ │ │ └── _funcs.py │ │ ├── partition_coefficient │ │ │ ├── __init__.py │ │ │ ├── _funcs.py │ │ │ └── gas_water_henry.csv │ │ ├── surface_tension │ │ │ ├── __init__.py │ │ │ └── _funcs.py │ │ ├── thermal_conductivity │ │ │ ├── __init__.py │ │ │ └── _funcs.py │ │ ├── vapor_pressure │ │ │ ├── __init__.py │ │ │ └── _funcs.py │ │ └── viscosity │ │ │ ├── __init__.py │ │ │ └── _funcs.py │ └── physics │ │ ├── __init__.py │ │ ├── _utils.py │ │ ├── ad_dif_conductance │ │ ├── __init__.py │ │ └── _funcs.py │ │ ├── capillary_pressure │ │ ├── __init__.py │ │ └── _funcs.py │ │ ├── diffusive_conductance │ │ ├── __init__.py │ │ └── _funcs.py │ │ ├── electrical_conductance │ │ ├── __init__.py │ │ └── _funcs.py │ │ ├── hydraulic_conductance │ │ ├── __init__.py │ │ └── _funcs.py │ │ ├── meniscus │ │ ├── __init__.py │ │ └── _funcs.py │ │ ├── multiphase │ │ ├── __init__.py │ │ └── _funcs.py │ │ ├── source_terms │ │ ├── __init__.py │ │ └── _funcs.py │ │ └── thermal_conductance │ │ ├── __init__.py │ │ └── _funcs.py ├── network │ ├── __init__.py │ ├── _bcc.py │ ├── _cubic.py │ ├── _cubic_template.py │ ├── _delaunay.py │ ├── _delaunay_voronoi_dual.py │ ├── _demo.py │ ├── _fcc.py │ ├── _network.py │ └── _voronoi.py ├── phase │ ├── __init__.py │ ├── _air.py │ ├── _mercury.py │ ├── _mixture.py │ ├── _phase.py │ ├── _species.py │ └── _water.py ├── solvers │ ├── __init__.py │ ├── _base.py │ ├── _pardiso.py │ ├── _petsc.py │ ├── _pyamg.py │ └── _scipy.py ├── topotools │ ├── __init__.py │ ├── _graphtools.py │ ├── _perctools.py │ └── _topotools.py ├── utils │ ├── __init__.py │ ├── _health.py │ ├── _misc.py │ ├── _project.py │ ├── _settings.py │ ├── _workspace.py │ └── jgf_schema.pkl └── visualization │ ├── __init__.py │ ├── _conduit_visualizer.py │ └── _plottools.py ├── pytest.ini ├── requirements.txt ├── requirements ├── conda.txt ├── docs.txt └── tests.txt ├── scripts ├── chemical_example.py ├── example_advection_diffusion ├── example_bundle_of_tubes.py ├── example_knudsen_diffusion.py ├── example_mass_partitioning.py ├── example_mixtures.py ├── example_multiphase_diffusion.py ├── example_multiphysics_solver.py ├── example_nernst_planck2d ├── example_nernst_planck3d ├── example_new_solvers.py ├── example_non_newtonian.py ├── example_pnflow.py ├── example_salome_export.py ├── example_statoil.py ├── example_subdomains.py ├── example_transient_diffusion.py ├── example_transient_dispersion.py ├── example_transient_nernst_planck └── tmp_comsol_data.npz ├── setup.cfg ├── setup.py └── tests ├── fixtures ├── 3DMA-Castlegate │ ├── castle_cln.np2th │ └── castle_cln.th2np ├── ICL-SandPack(F42A) │ ├── F42A_link1.dat │ ├── F42A_link2.dat │ ├── F42A_node1.dat │ └── F42A_node2.dat ├── ICL-Sandstone(Berea) │ ├── Berea_link1.dat │ ├── Berea_link2.dat │ ├── Berea_node1.dat │ └── Berea_node2.dat ├── JSONGraphFormat │ ├── invalid.json │ └── valid.json ├── OpenPNM-Objects │ └── net_01.net ├── PerGeos │ ├── flooded.am │ ├── mandatory.am │ └── simplePNM.am ├── VTK-VTP │ ├── test.pvsm │ ├── test_save_vtk_1.vtp │ └── test_save_vtk_2.vtp ├── berea_100_to_300.npz └── iMorph-Sandstone │ ├── throats_cellsThroatsGraph.txt │ └── throats_cellsThroatsGraph_Nodes.txt ├── integration ├── Flow_in_straight_conduit.py └── PoreSpyIO_on_Berea.py └── unit ├── algorithms ├── AdvectionDiffusionTest.py ├── BCTest.py ├── DrainageTest.py ├── GenericTransportTest.py ├── IPTest.py ├── NernstPlanckTest.py ├── ReactiveTransportTest.py ├── SolversTest.py ├── SubclassedTransportTest.py ├── TransientAdvectionDiffusionTest.py ├── TransientFickianDiffusionTest.py ├── TransientMultiPhysicsTest.py └── TransientReactiveTransportTest.py ├── core ├── Base2Test.py ├── BaseTest.py ├── HealthCheckTest.py └── ModelsWrapperTest.py ├── io ├── COMSOLTest.py ├── CSVTest.py ├── DictTest.py ├── HDF5Test.py ├── IOUtilsTest.py ├── JSONGraphFormatTest.py ├── MARockTest.py ├── NetworkXTest.py ├── PandasTest.py ├── ParaViewTest.py ├── PerGeosTest.py ├── PoreSpyTest.py ├── STLTest.py ├── SalomeTest.py ├── StatoilTest.py ├── VTKTest.py ├── XDMFTest.py ├── berea.net └── custom_code.py ├── models ├── geometry │ ├── ConduitDiffusiveSizeFactorsTest.py │ ├── ConduitHydraulicSizeFactorsTest.py │ ├── ConduitLengthsTest.py │ ├── PoreAreaTest.py │ ├── PoreCentroidTest.py │ ├── PoreSeedTest.py │ ├── PoreSizeTest.py │ ├── PoreSurfaceAreaTest.py │ ├── PoreVolumeTest.py │ ├── ThroatCapillaryShapeFactorTest.py │ ├── ThroatCentroidTest.py │ ├── ThroatCrossSectionalAreaTest.py │ ├── ThroatEndpointsTest.py │ ├── ThroatLengthTest.py │ ├── ThroatNormalTest.py │ ├── ThroatOffsetVerticesTest.py │ ├── ThroatPerimeterTest.py │ ├── ThroatSIzeTest.py │ ├── ThroatSeedTest.py │ ├── ThroatSurfaceAreaTest.py │ ├── ThroatVectorTest.py │ └── ThroatVolumeTest.py ├── misc │ └── MiscTest.py ├── network │ └── TestTopologyModels.py ├── phase │ ├── DensityTest.py │ ├── DiffusivityTest.py │ ├── HeatCapacityTest.py │ ├── LatentHeatOfVaporizationTest.py │ ├── MixturesTest.py │ ├── SurfaceTensionTest.py │ ├── ThermalConductivityTest.py │ ├── VaporPressureTest.py │ └── ViscosityTest.py └── physics │ ├── AdvectiveDiffusiveConductanceTest.py │ ├── CapillaryPressureTest.py │ ├── DiffusiveConductanceTest.py │ ├── ElectricalConductanceTest.py │ ├── HydraulicConductanceTest.py │ ├── MeniscusTest.py │ ├── MultiPhaseModelsTest.py │ ├── SourceTermTest.py │ └── ThermalConductanceTest.py ├── network ├── BravaisTest.py ├── CubicTemplateTest.py ├── CubicTest.py ├── DelaunayTest.py ├── GenericNetworkTest.py ├── LabelTest.py ├── NetworkHealthCheckTest.py ├── VoronoiDelaunayDualTest.py └── VoronoiTest.py ├── phase ├── AirTest.py ├── MercuryTest.py ├── MixtureTest.py ├── MultiPhaseTest.py ├── PhaseTest.py ├── SpeciesTest.py └── WaterTest.py ├── skgraph ├── test_generator_tools.py ├── test_generators.py ├── test_operations.py ├── test_queries.py ├── test_qupc.py └── test_tools.py ├── topotools ├── .pylint.d │ └── TestGeneratorTools1.stats ├── GraphToolsTest.py ├── PerctoolsTest.py ├── TopotoolsTest.py └── TransformationsTest.py ├── utils ├── ProjectTest.py ├── SettingsTest.py ├── UtilsTest.py └── WorkspaceTest.py └── visualization └── PlotToolsTest.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | 3 | source = 4 | openpnm 5 | 6 | [report] 7 | 8 | omit = 9 | */__init__.py 10 | docs/* 11 | example.py 12 | examples/* 13 | openpnm/_skgraph/visualization/* 14 | openpnm/solvers/_petsc.py 15 | openpnm/__version__.py 16 | openpnm/visualization/* 17 | scripts/* 18 | setup.py 19 | tests/* 20 | 21 | exclude_lines = 22 | pragma: no cover 23 | raise NotImplementedError 24 | if __name__ == .__main__.: 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: Concise description of the bug 5 | labels: bug, triage 6 | assignees: '' 7 | 8 | --- 9 | 10 | _(Note, you don't have to fill out every section here. They're just here for guidance. That said, nicely detailed feature requests are more likely to get dev attention sooner)_ 11 | 12 | **Describe the bug** 13 | A clear and concise description of what the bug is. 14 | 15 | **To Reproduce** 16 | Steps to reproduce the behavior, ideally a code snippet that only reproduces the bug: 17 | ```python 18 | paste the code snippet here 19 | ``` 20 | 21 | **Expected behavior** 22 | A clear and concise description of what you expected to happen. 23 | 24 | **Screenshots** 25 | If applicable, add screenshots to help explain your problem. 26 | 27 | **Additional context** 28 | Add any other context about the problem here. 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: Concise description of the feature 5 | labels: feature request, proposal 6 | assignees: '' 7 | 8 | --- 9 | 10 | _(Note, you don't have to fill out every section here. They're just here for guidance. That said, nicely detailed feature requests are more likely to get dev attention sooner)_ 11 | 12 | **Is your feature request related to a problem? Please describe.** 13 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 14 | 15 | **Describe the solution you'd like** 16 | A clear and concise description of what you want to happen. 17 | 18 | **Describe alternatives you've considered** 19 | A clear and concise description of any alternative solutions or features you've considered. 20 | 21 | **Additional context** 22 | Add any other context or screenshots about the feature request here. 23 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | # .github/release.yml 2 | 3 | changelog: 4 | exclude: 5 | labels: 6 | - ignore-for-release 7 | authors: 8 | - octocat 9 | categories: 10 | - title: Breaking Changes ⚠️ 11 | labels: 12 | - Semver-Major 13 | - breaking-change 14 | - api 15 | - title: New Features 🎉 16 | labels: 17 | - Semver-Minor 18 | - enhancement 19 | - new 20 | - title: Maintenance 🛠 21 | labels: 22 | - Semver-Patch 23 | - maint 24 | - title: Documentation 📗 25 | labels: 26 | - doc 27 | - example 28 | - title: Big Fixes 🐛 29 | labels: 30 | - Semver-Patch 31 | - bug 32 | - title: Other Changes ❓ 33 | labels: 34 | - "*" 35 | -------------------------------------------------------------------------------- /.github/workflows/bump-version-dev.yml: -------------------------------------------------------------------------------- 1 | name: Bump Version (dev) 2 | 3 | on: 4 | push: 5 | branches: 6 | - dev 7 | 8 | jobs: 9 | build: 10 | if: (! contains(github.event.head_commit.message, '[no bump]')) 11 | 12 | name: Bump version 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | 18 | - name: Set up Python 19 | uses: actions/setup-python@v4 20 | with: 21 | python-version: '3.9' 22 | 23 | - name: Set env variables 24 | run: | 25 | # The next line is very important, otherwise the line after triggers 26 | # git to track the permission change, which breaks bump2version API (needs clean git folder) 27 | git config core.filemode false 28 | chmod +x .github/workflows/utils.sh 29 | echo "VERSION_FILE=openpnm/__version__.py" >> $GITHUB_ENV 30 | echo "SETUP_CFG_FILE=setup.cfg" >> $GITHUB_ENV 31 | echo "${{ github.event.head_commit.message }}" 32 | 33 | - name: Install dependencies 34 | run: | 35 | pip install bump2version 36 | 37 | - name: Bump version (build) 38 | run: | 39 | source .github/workflows/utils.sh 40 | bump_version build $VERSION_FILE 41 | # Note that we don't want to create a new tag for "builds" 42 | 43 | # - name: Commit files 44 | # run: | 45 | # REPOSITORY=${INPUT_REPOSITORY:-$GITHUB_REPOSITORY} 46 | # remote_repo="https://${GITHUB_ACTOR}:${{ secrets.PUSH_ACTION_TOKEN }}@github.com/${REPOSITORY}.git" 47 | 48 | # git config --local user.email "action@github.com" 49 | # git config --local user.name "GitHub Action" 50 | 51 | # # Commit version bump to dev ([no ci] to avoid infinite loop) 52 | # git commit -m "Bump version number (build) [no ci]" -a 53 | # git push "${remote_repo}" dev 54 | 55 | - name: Commit files 56 | uses: stefanzweifel/git-auto-commit-action@v4 57 | with: 58 | commit_message: Bump version number (build part) 59 | commit_author: Author 60 | -------------------------------------------------------------------------------- /.github/workflows/cleanup-tags.yml: -------------------------------------------------------------------------------- 1 | name: Clean Up Tags 2 | 3 | on: [workflow_dispatch] 4 | 5 | jobs: 6 | deploy: 7 | name: Clean up tags 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v3 12 | 13 | - name: Clean up tags 14 | run: | 15 | git config --local user.email "action@github.com" 16 | git config --local user.name "GitHub Action" 17 | REPOSITORY=${INPUT_REPOSITORY:-$GITHUB_REPOSITORY} 18 | remote_repo="https://${GITHUB_ACTOR}:${{ secrets.GITHUB_TOKEN }}@github.com/${REPOSITORY}.git" 19 | git fetch --all --tags --force 20 | prefix="v" 21 | pattern="^($prefix)(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(?:-((?:0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$" 22 | git tag | grep --invert-match -P $pattern | xargs -n 1 -I % git push "${remote_repo}" :refs/tags/% 23 | -------------------------------------------------------------------------------- /.github/workflows/examples.yml: -------------------------------------------------------------------------------- 1 | name: Examples 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ${{ matrix.os }} 9 | defaults: 10 | run: 11 | shell: bash 12 | 13 | strategy: 14 | max-parallel: 1 15 | matrix: 16 | python-version: ['3.9'] 17 | os: [ubuntu-latest] 18 | 19 | steps: 20 | - uses: actions/checkout@v3 21 | - name: Set up Python ${{ matrix.python-version }} 22 | uses: actions/setup-python@v4 23 | with: 24 | python-version: ${{ matrix.python-version }} 25 | 26 | - name: Cache pip 27 | uses: actions/cache@v3 28 | with: 29 | path: ~/.cache/pip 30 | key: ${{ runner.os }}-pip-${{ hashFiles('setup.py') }} 31 | restore-keys: | 32 | ${{ runner.os }}-pip- 33 | 34 | - name: Install dependencies (pip) 35 | run: | 36 | pip install \ 37 | -r requirements.txt \ 38 | -r requirements/tests.txt 39 | pip install pypardiso 40 | 41 | - name: Running tests 42 | run: 43 | pytest examples/ 44 | --nbval-lax 45 | -------------------------------------------------------------------------------- /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Documentation 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | branches: 7 | - release 8 | 9 | jobs: 10 | deploy-docs: 11 | 12 | runs-on: ubuntu-latest 13 | defaults: 14 | run: 15 | shell: bash -l {0} 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Set up Python 20 | uses: actions/setup-python@v4 21 | with: 22 | python-version: '3.10' 23 | 24 | - name: Cache pip 25 | uses: actions/cache@v3 26 | with: 27 | # This path is specific to Ubuntu 28 | path: ~/.cache/pip 29 | # Look to see if there is a cache hit for the corresponding requirements file 30 | key: ${{ runner.os }}-pip-${{ hashFiles('setup.py') }} 31 | restore-keys: | 32 | ${{ runner.os }}-pip- 33 | ${{ runner.os }}- 34 | 35 | - name: Install dependencies 36 | run: | 37 | pip install -r requirements.txt 38 | pip install -r requirements/docs.txt 39 | pip install pypardiso 40 | 41 | - name: Build the documentation 42 | run: | 43 | cd docs 44 | make html 45 | 46 | - name: GitHub Pages action 47 | uses: peaceiris/actions-gh-pages@v3 48 | with: 49 | github_token: ${{ secrets.GITHUB_TOKEN }} 50 | publish_dir: ./docs/_build/html 51 | cname: openpnm.org 52 | -------------------------------------------------------------------------------- /.github/workflows/nightly.yml: -------------------------------------------------------------------------------- 1 | name: Nightly Builds 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * *" 6 | 7 | jobs: 8 | nightly: 9 | 10 | runs-on: ${{ matrix.os }} 11 | defaults: 12 | run: 13 | shell: bash 14 | 15 | strategy: 16 | fail-fast: false 17 | max-parallel: 9 18 | matrix: 19 | python-version: ['3.9', '3.10', '3.11'] 20 | # Add macos-latest to the next line once #2451 is fixed 21 | os: [ubuntu-latest, windows-latest, macos-latest] 22 | include: 23 | - os: ubuntu-latest 24 | path: ~/.cache/pip 25 | - os: macos-latest 26 | path: ~/Library/Caches/pip 27 | - os: windows-latest 28 | path: ~\AppData\Local\pip\Cache 29 | 30 | steps: 31 | - name: Cache pip 32 | uses: actions/cache@v3 33 | with: 34 | path: ${{ matrix.path }} 35 | key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} 36 | restore-keys: | 37 | ${{ runner.os }}-pip- 38 | 39 | - uses: actions/checkout@v3 40 | 41 | - name: Set up Python ${{ matrix.python-version }} 42 | uses: actions/setup-python@v4 43 | with: 44 | python-version: ${{ matrix.python-version }} 45 | 46 | - name: Install dependencies 47 | run: | 48 | pip install \ 49 | -r requirements.txt \ 50 | -r requirements/tests.txt 51 | 52 | - name: Install pypardiso on non-macOS 53 | if: (matrix.os != 'macos-latest') 54 | run: | 55 | pip install pypardiso 56 | 57 | - name: Running tests 58 | run: 59 | pytest . --color=yes 60 | -------------------------------------------------------------------------------- /.github/workflows/release-notes.yml: -------------------------------------------------------------------------------- 1 | name: Release Notes 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | build: 10 | name: Release notes 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v2 16 | with: 17 | fetch-depth: 0 # to retrieve entire history of refs/tags 18 | 19 | - name: Generate release notes 20 | run: | 21 | git fetch --all --tags --force 22 | chmod +x .github/workflows/logger.sh 23 | chmod +x .github/workflows/utils.sh 24 | source .github/workflows/utils.sh 25 | bash .github/workflows/logger.sh 26 | echo "TAG=$(get_most_recent_tag)" >> $GITHUB_ENV 27 | 28 | - name: Create GitHub release 29 | uses: Roang-zero1/github-create-release-action@master 30 | with: 31 | version_regex: ^v[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+ 32 | create_draft: true 33 | created_tag: ${{ env.TAG }} 34 | update_existing: false 35 | release_title: ${{ env.TAG }} 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 38 | -------------------------------------------------------------------------------- /.github/workflows/style-check.yml: -------------------------------------------------------------------------------- 1 | name: flake8 Lint 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | flake8-lint: 7 | runs-on: ubuntu-latest 8 | name: Lint 9 | steps: 10 | - name: Check out source repository 11 | uses: actions/checkout@v2 12 | - name: Set up Python environment 13 | uses: actions/setup-python@v2 14 | with: 15 | python-version: '3.10' 16 | - name: flake8 Lint 17 | uses: py-actions/flake8@v2 18 | with: 19 | ignore: "D100,E226,F403,N806,W503,WPS111,WPS211,WPS237,WPS300,WPS305,WPS319,WPS347,WPS410,WPS437" 20 | exclude: "doc/,setup.py,scripts/,tests/" 21 | max-line-length: "90" 22 | path: ".." 23 | plugins: "flake8-bugbear==22.1.11 flake8-black" 24 | -------------------------------------------------------------------------------- /.github/workflows/verify-pip-installation.yml: -------------------------------------------------------------------------------- 1 | name: Verify pip-installability 2 | 3 | on: [workflow_dispatch] 4 | 5 | jobs: 6 | deploy: 7 | name: Verify pip-installability 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v3 12 | - name: Set up Python 13 | uses: actions/setup-python@v4 14 | with: 15 | python-version: '3.9' 16 | 17 | - name: Set branch name as env variable 18 | run: | 19 | echo Current branch: ${GITHUB_REF#refs/heads/} 20 | echo "BRANCH_NAME=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV 21 | 22 | - name: Install only using pip 23 | run: 24 | pip install git+https://github.com/PMEAL/OpenPNM.git@${BRANCH_NAME} 25 | 26 | - name: Test the installation 27 | run: 28 | python -c "import openpnm" 29 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | message: "If you use this software, please cite it as below." 3 | authors: 4 | - family-names: "Gostick" 5 | given-names: "Jeff" 6 | orcid: "https://orcid.org/0000-0001-7736-7124" 7 | title: "OpenPNM" 8 | url: "https://github.com/PMEAL/OpenPNM" 9 | preferred-citation: 10 | type: article 11 | authors: 12 | - family-names: "Gostick" 13 | given-names: "Jeff" 14 | orcid: "https://orcid.org/0000-0001-7736-7124" 15 | - family-names: "Aghighi" 16 | given-names: "Mahmoudreza" 17 | - family-names: "Hinebaugh" 18 | given-names: "James" 19 | - family-names: "Tranter" 20 | given-names: "Tom" 21 | - family-names: "Hoeh" 22 | given-names: "Michael A." 23 | - family-names: "Day" 24 | given-names: "Harold" 25 | - family-names: "Spellacy" 26 | given-names: "Brennan" 27 | - family-names: "Sharqawy" 28 | given-names: "Mostafa H." 29 | - family-names: "Bazylak" 30 | given-names: "Aimy" 31 | - family-names: "Burns" 32 | given-names: "Lehnert" 33 | - family-names: "Werner" 34 | given-names: "Harold" 35 | - family-names: "Putz" 36 | given-names: "Andreas" 37 | doi: "10.1109/MCSE.2016.49" 38 | journal: "Computing in Science & Engineering" 39 | start: 60 # First page number 40 | end: 74 # Last page number 41 | title: "OpenPNM: a pore network modeling package" 42 | year: 2016 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 PMEAL 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .SILENT: clean nbtest test 2 | .PHONY: clean nbtest test 3 | 4 | clean: 5 | echo "Cleaning up the repo..." 6 | python bin/clean 7 | 8 | nbtest: 9 | echo "Running example notebooks..." 10 | pytest examples --nbval-lax \ 11 | --ignore="examples/paper_recreations/Blunt et al. (2013)" \ 12 | --ignore="examples/paper_recreations/Wu et al. (2010)" 13 | 14 | test: 15 | echo "Running unit tests..." 16 | pytest --ignore=scripts 17 | 18 | -------------------------------------------------------------------------------- /VERSIONING.md: -------------------------------------------------------------------------------- 1 | ## Release Management and Versioning 2 | 3 | `OpenPNM` uses [Semantic Versioning](http://semver.org) (i.e. X.Y.Z) to label releases. As of verion 2.3.2, all versions of OpenPNM are available on [PyPI](https://pypi.python.org/pypi). Prior to this, only major and minor version were pushed. 4 | 5 | All development occurs on `dev` via feature branches and the pull request functionality of Github. A new release is defined each time the `dev` branch is merged into the `release` branch. Several automations are setup so that upon each release, the code is automatically deployed to PyPi and Conda, and a release announcement is created on Github containing a summary of all the changes. This `dev` and `release` workflow replaces the previous approach based on gitflow. 6 | 7 | `OpenPNM` depends on several other packages widely known as the [Scipy Stack](https://www.scipy.org/stackspec.html). It is our policy to always support the latest version of all these packages and their dependencies. 8 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | branch: dev 3 | 4 | coverage: 5 | precision: 1 6 | round: down 7 | range: "50...100" 8 | 9 | status: 10 | project: 11 | default: 12 | target: auto 13 | threshold: 0.5% 14 | branches: null 15 | 16 | patch: 17 | default: 18 | target: auto 19 | threshold: 0.5% 20 | branches: null 21 | 22 | comment: 23 | layout: "header, diff, changes, sunburst, uncovered" 24 | branches: null 25 | behavior: default 26 | -------------------------------------------------------------------------------- /docs/_examples/applications.rst: -------------------------------------------------------------------------------- 1 | .. _applications: 2 | 3 | ############ 4 | Applications 5 | ############ 6 | 7 | .. tab-set:: 8 | 9 | .. tab-item:: Applications 10 | 11 | .. toctree:: 12 | :maxdepth: 1 13 | :glob: 14 | 15 | ../examples/applications/* 16 | -------------------------------------------------------------------------------- /docs/_examples/index.rst: -------------------------------------------------------------------------------- 1 | .. _examples_index: 2 | 3 | ######## 4 | Examples 5 | ######## 6 | 7 | The best way to learn OpenPNM (like anything) is to experiment. 8 | In this page, you can find lots of examples that will help you get started. 9 | The examples are categorized into the following categories: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | ../examples/getting_started 15 | tutorials 16 | applications 17 | reference 18 | -------------------------------------------------------------------------------- /docs/_examples/reference.rst: -------------------------------------------------------------------------------- 1 | .. _reference: 2 | 3 | ######### 4 | Reference 5 | ######### 6 | 7 | .. tab-set:: 8 | 9 | .. tab-item:: Software 10 | 11 | .. toctree:: 12 | :maxdepth: 1 13 | :glob: 14 | 15 | ../examples/reference/architecture/** 16 | 17 | .. tab-item:: Networks 18 | 19 | .. toctree:: 20 | :maxdepth: 2 21 | :glob: 22 | 23 | ../examples/reference/networks/** 24 | 25 | .. tab-item:: Simulations 26 | 27 | .. toctree:: 28 | :maxdepth: 2 29 | :glob: 30 | 31 | ../examples/reference/simulations/** 32 | -------------------------------------------------------------------------------- /docs/_examples/tutorials.rst: -------------------------------------------------------------------------------- 1 | .. _tutorials: 2 | 3 | ######################### 4 | 10 Steps to Learn OpenPNM 5 | ######################### 6 | 7 | .. tab-set:: 8 | 9 | .. tab-item:: Tutorials 10 | 11 | .. toctree:: 12 | :maxdepth: 1 13 | :glob: 14 | 15 | ../examples/tutorials/* 16 | -------------------------------------------------------------------------------- /docs/_static/images/conc_dist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PMEAL/OpenPNM/7fd91d9d5accafd8ac58f343b4ce16bc143640fe/docs/_static/images/conc_dist.png -------------------------------------------------------------------------------- /docs/_static/images/droplet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PMEAL/OpenPNM/7fd91d9d5accafd8ac58f343b4ce16bc143640fe/docs/_static/images/droplet.png -------------------------------------------------------------------------------- /docs/_static/images/extracted_berea.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PMEAL/OpenPNM/7fd91d9d5accafd8ac58f343b4ce16bc143640fe/docs/_static/images/extracted_berea.png -------------------------------------------------------------------------------- /docs/_static/images/find_neighbor_pores_three_labels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PMEAL/OpenPNM/7fd91d9d5accafd8ac58f343b4ce16bc143640fe/docs/_static/images/find_neighbor_pores_three_labels.png -------------------------------------------------------------------------------- /docs/_static/images/find_neighbor_pores_three_labels_version_three.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PMEAL/OpenPNM/7fd91d9d5accafd8ac58f343b4ce16bc143640fe/docs/_static/images/find_neighbor_pores_three_labels_version_three.png -------------------------------------------------------------------------------- /docs/_static/images/generic_geometry_histogram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PMEAL/OpenPNM/7fd91d9d5accafd8ac58f343b4ce16bc143640fe/docs/_static/images/generic_geometry_histogram.png -------------------------------------------------------------------------------- /docs/_static/images/histogram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PMEAL/OpenPNM/7fd91d9d5accafd8ac58f343b4ce16bc143640fe/docs/_static/images/histogram.png -------------------------------------------------------------------------------- /docs/_static/images/openpnm_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PMEAL/OpenPNM/7fd91d9d5accafd8ac58f343b4ce16bc143640fe/docs/_static/images/openpnm_logo.jpg -------------------------------------------------------------------------------- /docs/_static/images/percolation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PMEAL/OpenPNM/7fd91d9d5accafd8ac58f343b4ce16bc143640fe/docs/_static/images/percolation.png -------------------------------------------------------------------------------- /docs/_static/images/quick_start_drainage_curve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PMEAL/OpenPNM/7fd91d9d5accafd8ac58f343b4ce16bc143640fe/docs/_static/images/quick_start_drainage_curve.png -------------------------------------------------------------------------------- /docs/_static/images/reactive_diffusion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PMEAL/OpenPNM/7fd91d9d5accafd8ac58f343b4ce16bc143640fe/docs/_static/images/reactive_diffusion.png -------------------------------------------------------------------------------- /docs/_static/images/rel_diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PMEAL/OpenPNM/7fd91d9d5accafd8ac58f343b4ce16bc143640fe/docs/_static/images/rel_diff.png -------------------------------------------------------------------------------- /docs/_static/images/stick_and_ball_histogram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PMEAL/OpenPNM/7fd91d9d5accafd8ac58f343b4ce16bc143640fe/docs/_static/images/stick_and_ball_histogram.png -------------------------------------------------------------------------------- /docs/_static/images/transient.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PMEAL/OpenPNM/7fd91d9d5accafd8ac58f343b4ce16bc143640fe/docs/_static/images/transient.png -------------------------------------------------------------------------------- /docs/_templates/autosummary/base.rst: -------------------------------------------------------------------------------- 1 | {{ name | escape | underline }} 2 | 3 | .. currentmodule:: {{ module }} 4 | 5 | .. auto{{ objtype }}:: {{ objname }} 6 | -------------------------------------------------------------------------------- /docs/_templates/autosummary/class.rst: -------------------------------------------------------------------------------- 1 | {{ name }} 2 | {{ underline }} 3 | 4 | .. currentmodule:: {{ module }} 5 | 6 | .. autoclass:: {{ objname }} 7 | :no-members: 8 | :no-inherited-members: 9 | :no-special-members: 10 | 11 | {% block methods %} 12 | {% if methods %} 13 | .. HACK -- we don't want this to appear in the output, but autosummary should still generate the pages. 14 | .. autosummary:: 15 | :toctree: 16 | {% for item in all_methods %} 17 | {%- if not item.startswith('_') or item in ['__call__'] %} 18 | {{ name }}.{{ item }} 19 | {%- endif -%} 20 | {%- endfor %} 21 | {% endif %} 22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /docs/_templates/autosummary/method.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | {{ name }} 4 | {{ underline }} 5 | 6 | .. currentmodule:: {{ module }} 7 | 8 | .. automethod:: {{ objname }} 9 | -------------------------------------------------------------------------------- /docs/_templates/autosummary/module.rst: -------------------------------------------------------------------------------- 1 | :noindex: 2 | 3 | .. raw:: html 4 | 5 | 6 | 7 | {{ name | escape | underline }} 8 | 9 | .. automodule:: {{ fullname }} 10 | 11 | {% block attributes %} 12 | {% if attributes %} 13 | .. rubric:: {{ _('Module Attributes') }} 14 | 15 | .. autosummary:: 16 | {% for item in attributes %} 17 | {{ item }} 18 | {%- endfor %} 19 | {% endif %} 20 | {% endblock %} 21 | 22 | {% block functions %} 23 | {% if functions %} 24 | .. rubric:: {{ _('Functions') }} 25 | 26 | .. autosummary:: 27 | :toctree: 28 | {% for item in functions %} 29 | {{ item }} 30 | {%- endfor %} 31 | {% endif %} 32 | {% endblock %} 33 | 34 | {% block classes %} 35 | {% if classes %} 36 | .. rubric:: {{ _('Classes') }} 37 | 38 | .. autosummary:: 39 | :nosignatures: 40 | :toctree: 41 | 42 | {% for item in classes %} 43 | {{ item }} 44 | {%- endfor %} 45 | {% endif %} 46 | {% endblock %} 47 | 48 | {% block exceptions %} 49 | {% if exceptions %} 50 | .. rubric:: {{ _('Exceptions') }} 51 | 52 | .. autosummary:: 53 | {% for item in exceptions %} 54 | {{ item }} 55 | {%- endfor %} 56 | {% endif %} 57 | {% endblock %} 58 | 59 | {% block modules %} 60 | {% if modules %} 61 | .. rubric:: Modules 62 | 63 | .. autosummary:: 64 | :toctree: 65 | :recursive: 66 | {% for item in modules %} 67 | {{ item }} 68 | {%- endfor %} 69 | {% endif %} 70 | {% endblock %} 71 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. _front_page: 2 | 3 | .. module:: openpnm 4 | :noindex: 5 | 6 | .. image:: ./_static/images/openpnm_logo.jpg 7 | :name: banner 8 | 9 | ######################################### 10 | Pore Network Modeling Framework in Python 11 | ######################################### 12 | 13 | What is OpenPNM? |stars| 14 | ######################## 15 | 16 | OpenPNM is an open source project to provide porous media researchers 17 | with a ready-made framework for performing a wide range of pore network 18 | simulations. 19 | 20 | .. image:: https://user-images.githubusercontent.com/14086031/188250733-9d08db14-7822-4ba7-97a5-af148e1deabb.png 21 | :name: banner2 22 | 23 | .. toctree:: 24 | :hidden: 25 | :maxdepth: 0 26 | 27 | installation 28 | modules/index 29 | _examples/index 30 | 31 | ---- 32 | 33 | How To Cite 34 | ########### 35 | 36 | If you use OpenPNM in a publication, please add the following citation: 37 | 38 | .. admonition:: Citation 39 | 40 | Gostick J, Aghighi M, Hinebaugh J, Tranter T, Hoeh MA, Day H, 41 | Spellacy B, Sharqawy MH, Bazylak A, Burns A, Lehnert W. 42 | **OpenPNM: a pore network modeling package**. *Computing in 43 | Science & Engineering*. 2016 May 25;18(4):60-74. 44 | `doi:10.1109/MCSE.2016.49 `_. 45 | 46 | .. |stars| image:: https://img.shields.io/github/stars/PMEAL/OpenPNM.svg?style=social&label=Star&maxAge=2592000 47 | :target: https://GitHub.com/PMEAL/OpenPNM/stargazers/ 48 | -------------------------------------------------------------------------------- /docs/modules/index.rst: -------------------------------------------------------------------------------- 1 | .. _modules_index: 2 | 3 | ################ 4 | Module Reference 5 | ################ 6 | 7 | .. automodule:: openpnm 8 | 9 | .. autosummary:: 10 | :toctree: generated 11 | :recursive: 12 | 13 | openpnm.utils 14 | openpnm.core 15 | openpnm.network 16 | openpnm.phase 17 | openpnm.models 18 | openpnm.solvers 19 | openpnm.integrators 20 | openpnm.algorithms 21 | openpnm.topotools 22 | openpnm.io 23 | openpnm.contrib 24 | openpnm.visualization 25 | -------------------------------------------------------------------------------- /examples/applications/Sandstone.csv: -------------------------------------------------------------------------------- 1 | Pc [Pa],S_Hg 2 | 12056.4,0.00122 3 | 15162.4,0.00368 4 | 17630,0.00741 5 | 23693,0.0086 6 | 30708,0.01607 7 | 36799,0.01979 8 | 44633,0.02351 9 | 49752,0.02599 10 | 52522,0.03474 11 | 55439,0.05476 12 | 55449,0.05473 13 | 57820,0.06977 14 | 59569,0.0998 15 | 62089,0.16613 16 | 63563,0.22244 17 | 65092,0.25123 18 | 66585,0.37262 19 | 68232,0.34759 20 | 69833,0.42518 21 | 71069,0.471 22 | 71085,0.45271 23 | 76355,0.54039 24 | 76802,0.54156 25 | 101289,0.60534 26 | 117732,0.63785 27 | 135188,0.67913 28 | 160014,0.70663 29 | 203630,0.73037 30 | 222870,0.74787 31 | 267020,0.76787 32 | 348080,0.7966 33 | 437710,0.80783 34 | 696280,0.83528 35 | 1148210,0.87899 36 | 1860000,0.90268 37 | 2352900,0.92016 38 | 3975500,0.94885 39 | 5214900,0.95882 40 | 7353000,0.98253 41 | 9821400,0.99375 42 | 11698800,0.99622 43 | 12884400,0.9962 44 | 15440700,0.99617 45 | 17108000,0.99865 46 | -------------------------------------------------------------------------------- /examples/contrib/README.rst: -------------------------------------------------------------------------------- 1 | 2 | :orphan: 3 | 4 | Contibuted Examples 5 | ------------------- 6 | 7 | This folder contains code that has been contributed by a user (or developer) that is not quite ready to be included in OpenPNM permanently, but is worth including in the repository so it can be accessible via Github. 8 | 9 | The files in this folder are NOT included in the testing suite so they are not gauranteed to work. Usually only a small fix is required to get them going. It is generally encourage to include the ``openpnm.__version__`` number in the code so readers know at least which version of openpnm it was intened to work with. 10 | -------------------------------------------------------------------------------- /examples/reference/simulations/available_matrix_solvers.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Available Matrix Solvers" 8 | ] 9 | } 10 | ], 11 | "metadata": { 12 | "@webio": { 13 | "lastCommId": null, 14 | "lastKernelId": null 15 | }, 16 | "kernelspec": { 17 | "display_name": "Python 3", 18 | "language": "python", 19 | "name": "python3" 20 | }, 21 | "language_info": { 22 | "codemirror_mode": { 23 | "name": "ipython", 24 | "version": 3 25 | }, 26 | "file_extension": ".py", 27 | "mimetype": "text/x-python", 28 | "name": "python", 29 | "nbconvert_exporter": "python", 30 | "pygments_lexer": "ipython3", 31 | "version": "3.8.12" 32 | } 33 | }, 34 | "nbformat": 4, 35 | "nbformat_minor": 2 36 | } 37 | -------------------------------------------------------------------------------- /examples/reference/simulations/transient_transport.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "b8674439", 6 | "metadata": {}, 7 | "source": [ 8 | "# Transient Transport\n", 9 | "\n", 10 | "This notebook is stil a work in progress..." 11 | ] 12 | } 13 | ], 14 | "metadata": { 15 | "kernelspec": { 16 | "display_name": "Python 3 (ipykernel)", 17 | "language": "python", 18 | "name": "python3" 19 | }, 20 | "language_info": { 21 | "codemirror_mode": { 22 | "name": "ipython", 23 | "version": 3 24 | }, 25 | "file_extension": ".py", 26 | "mimetype": "text/x-python", 27 | "name": "python", 28 | "nbconvert_exporter": "python", 29 | "pygments_lexer": "ipython3", 30 | "version": "3.9.7" 31 | } 32 | }, 33 | "nbformat": 4, 34 | "nbformat_minor": 5 35 | } 36 | -------------------------------------------------------------------------------- /openpnm/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | ======= 3 | OpenPNM 4 | ======= 5 | 6 | OpenPNM is a package for performing pore network simulations of transport in 7 | porous materials. 8 | 9 | """ 10 | 11 | import logging 12 | 13 | from rich.logging import RichHandler 14 | 15 | FORMAT = "%(message)s" 16 | logging.basicConfig( 17 | format=FORMAT, datefmt="[%X]", handlers=[RichHandler(rich_tracebacks=True)] 18 | ) 19 | 20 | import numpy as _np 21 | 22 | from . import ( 23 | _skgraph, 24 | algorithms, 25 | contrib, 26 | core, 27 | integrators, 28 | io, 29 | models, 30 | network, 31 | phase, 32 | solvers, 33 | topotools, 34 | utils, 35 | visualization, 36 | ) 37 | from .utils import Project, Workspace 38 | 39 | _np.seterr(divide='ignore', invalid='ignore') 40 | 41 | __version__ = utils._get_version() 42 | -------------------------------------------------------------------------------- /openpnm/__version__.py: -------------------------------------------------------------------------------- 1 | __version__ = '3.5.0.dev2' 2 | -------------------------------------------------------------------------------- /openpnm/_skgraph/generators/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Generators 3 | ========== 4 | 5 | This module contains a selection of functions that generate various network 6 | topologies 7 | 8 | """ 9 | 10 | from ._cubic import cubic 11 | from ._delaunay import delaunay 12 | from ._gabriel import gabriel 13 | from ._voronoi import voronoi 14 | from ._voronoi_delaunay_dual import voronoi_delaunay_dual 15 | from ._template import cubic_template 16 | from ._fcc import fcc 17 | from ._bcc import bcc 18 | from . import tools 19 | -------------------------------------------------------------------------------- /openpnm/_skgraph/generators/_template.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from openpnm._skgraph.generators import cubic 3 | from openpnm._skgraph.operations import trim_nodes 4 | 5 | 6 | def cubic_template(template, spacing=1, connectivity=6, 7 | node_prefix='node', edge_prefix='edge'): 8 | r""" 9 | Generate a simple cubic lattice matching the shape of the provided tempate 10 | 11 | Parameters 12 | ---------- 13 | templte : ndarray 14 | Each ``True`` value will be treated as a vertex while all others 15 | will be trimmed. 16 | spacing : array_like or float 17 | The size of a unit cell in each direction. If an scalar is given it is 18 | applied in all 3 directions. 19 | 20 | Returns 21 | ------- 22 | network : dict 23 | A dictionary containing 'node.coords' and 'edge.conns' 24 | 25 | """ 26 | template = np.atleast_3d(template).astype(bool) 27 | # Generate a full cubic network 28 | temp = cubic(shape=template.shape, spacing=spacing, 29 | connectivity=connectivity, 30 | node_prefix=node_prefix, edge_prefix=edge_prefix) 31 | # Store some info about template 32 | coords = np.unravel_index(range(template.size), template.shape) 33 | coords = np.vstack(coords).T 34 | Np = coords.shape[0] 35 | temp[node_prefix+'.template_coords'] = coords 36 | temp[node_prefix+'.template_indices'] = np.arange(Np) 37 | # Trim pores not present in template 38 | temp = trim_nodes(network=temp, inds=~template.flatten()) 39 | return temp 40 | 41 | 42 | if __name__ == '__main__': 43 | im = np.ones([50, 50], dtype=bool) 44 | im[25:, ...] = False 45 | net = cubic_template(template=im) 46 | print(net.keys()) 47 | -------------------------------------------------------------------------------- /openpnm/_skgraph/generators/network: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PMEAL/OpenPNM/7fd91d9d5accafd8ac58f343b4ce16bc143640fe/openpnm/_skgraph/generators/network -------------------------------------------------------------------------------- /openpnm/_skgraph/generators/tools/__init__.py: -------------------------------------------------------------------------------- 1 | from ._funcs import * 2 | -------------------------------------------------------------------------------- /openpnm/_skgraph/io/__init__.py: -------------------------------------------------------------------------------- 1 | from ._funcs import * 2 | -------------------------------------------------------------------------------- /openpnm/_skgraph/io/_funcs.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PMEAL/OpenPNM/7fd91d9d5accafd8ac58f343b4ce16bc143640fe/openpnm/_skgraph/io/_funcs.py -------------------------------------------------------------------------------- /openpnm/_skgraph/operations/__init__.py: -------------------------------------------------------------------------------- 1 | from ._funcs import * 2 | from ._binary import * 3 | from ._unary import * 4 | -------------------------------------------------------------------------------- /openpnm/_skgraph/operations/_funcs.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | __all__ = [ 4 | 5 | ] 6 | -------------------------------------------------------------------------------- /openpnm/_skgraph/queries/QuickUnion.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PMEAL/OpenPNM/7fd91d9d5accafd8ac58f343b4ce16bc143640fe/openpnm/_skgraph/queries/QuickUnion.py -------------------------------------------------------------------------------- /openpnm/_skgraph/queries/__init__.py: -------------------------------------------------------------------------------- 1 | from ._funcs import * 2 | from ._qupc import * 3 | -------------------------------------------------------------------------------- /openpnm/_skgraph/queries/_qupc.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numba import njit 3 | from scipy.stats import rankdata 4 | 5 | 6 | __all__ = [ 7 | 'qupc_initialize', 8 | 'qupc_update', 9 | 'qupc_compress', 10 | 'qupc_reduce', 11 | ] 12 | 13 | 14 | @njit 15 | def qupc_initialize(size): 16 | return np.arange(size, dtype=np.int_) 17 | 18 | 19 | @njit 20 | def qupc_update(arr, ind, val): 21 | if ind == val: 22 | arr[ind] = val 23 | else: 24 | # Update array and do path compression simultaneously 25 | while arr[ind] != val: 26 | arr[ind] = arr[val] 27 | ind = val 28 | val = arr[val] 29 | return arr 30 | 31 | 32 | def qupc_compress(arr): 33 | temp = rankdata(arr, method='dense') 34 | arr[:] = temp 35 | arr -= 1 36 | return arr 37 | 38 | 39 | @njit 40 | def qupc_reduce(arr): 41 | for i in range(len(arr)-1, 0, -1): 42 | arr[i] = arr[arr[i]] 43 | return arr 44 | 45 | 46 | if __name__ == '__main__': 47 | a = qupc_initialize(10) 48 | qupc_update(a, 4, 2) 49 | qupc_update(a, 7, 4) 50 | qupc_update(a, 9, 6) 51 | qupc_update(a, 6, 2) 52 | qupc_update(a, 5, 9) 53 | assert np.all(a == [0, 1, 2, 3, 2, 6, 2, 2, 8, 6]) 54 | qupc_reduce(a) 55 | assert np.all(a == [0, 1, 2, 3, 2, 2, 2, 2, 8, 2]) 56 | qupc_update(a, 9, 9) 57 | qupc_update(a, 0, 1) 58 | qupc_update(a, 8, 0) 59 | assert np.all(a == [1, 1, 2, 3, 2, 2, 2, 2, 1, 9]) 60 | qupc_reduce(a) 61 | qupc_compress(a) 62 | assert np.all(a == [0, 0, 1, 2, 1, 1, 1, 1, 0, 3]) 63 | print(a) 64 | -------------------------------------------------------------------------------- /openpnm/_skgraph/simulations/__init__.py: -------------------------------------------------------------------------------- 1 | from ._funcs import * 2 | from ._percolation import * 3 | -------------------------------------------------------------------------------- /openpnm/_skgraph/simulations/_funcs.py: -------------------------------------------------------------------------------- 1 | import scipy.sparse as sprs 2 | import numpy as np 3 | from scipy.sparse import csgraph 4 | 5 | 6 | __all__ = [ 7 | 8 | ] 9 | -------------------------------------------------------------------------------- /openpnm/_skgraph/tools/GraphBundle.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PMEAL/OpenPNM/7fd91d9d5accafd8ac58f343b4ce16bc143640fe/openpnm/_skgraph/tools/GraphBundle.py -------------------------------------------------------------------------------- /openpnm/_skgraph/tools/__init__.py: -------------------------------------------------------------------------------- 1 | from ._coords_transforms import * 2 | from ._funcs import * 3 | -------------------------------------------------------------------------------- /openpnm/_skgraph/visualization/__init__.py: -------------------------------------------------------------------------------- 1 | from ._funcs import * 2 | -------------------------------------------------------------------------------- /openpnm/algorithms/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Algorithms 3 | ========== 4 | 5 | This module contains various algorithms for performing transport simulations 6 | 7 | 8 | """ 9 | 10 | from ._algorithm import * 11 | from ._transport import * 12 | 13 | from ._reactive_transport import * 14 | from ._transient_reactive_transport import * 15 | 16 | from ._stokes_flow import * 17 | 18 | from ._fickian_diffusion import * 19 | from ._transient_fickian_diffusion import * 20 | 21 | from ._advection_diffusion import * 22 | from ._transient_advection_diffusion import * 23 | 24 | from ._fourier_conduction import * 25 | from ._transient_fourier_conduction import * 26 | 27 | from ._ohmic_conduction import * 28 | 29 | from ._drainage import * 30 | from ._invasion_percolation import * 31 | -------------------------------------------------------------------------------- /openpnm/algorithms/_fickian_diffusion.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from openpnm.algorithms import ReactiveTransport 3 | from openpnm.utils import Docorator 4 | 5 | 6 | docstr = Docorator() 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | __all__ = ['FickianDiffusion'] 11 | 12 | 13 | @docstr.dedent 14 | class FickianDiffusionSettings(): 15 | r""" 16 | 17 | Parameters 18 | ---------- 19 | %(ReactiveTransportSettings.parameters)s 20 | 21 | """ 22 | quantity = 'pore.concentration' 23 | conductance = 'throat.diffusive_conductance' 24 | 25 | 26 | @docstr.dedent 27 | class FickianDiffusion(ReactiveTransport): 28 | r""" 29 | A class to simulate binary diffusion with reactions 30 | 31 | Parameters 32 | ---------- 33 | %(ReactiveTransport.parameters)s 34 | 35 | Notes 36 | ----- 37 | Fickian diffusion in porous materials occurs in the void space, but 38 | becuase the diffusion is confined to pores it is impacted by the porosity 39 | and tortuosity of the network. Thus the total diffusive flux through the 40 | network is reduced. This class can be used to simualte diffusion-reaction 41 | in domains with specified boundary conditions, or it can be used 42 | to calculate the effective diffusivity of the network by applying 43 | controlled boundary conditions on opposing faces, calculating the 44 | diffusion rate, and inverting Fick's first law as follows: 45 | 46 | .. math:: 47 | 48 | D_{eff} = N_{A}*L/(A*\Delta C_{A}) 49 | 50 | """ 51 | 52 | def __init__(self, name='fick_?', **kwargs): 53 | super().__init__(name=name, **kwargs) 54 | self.settings._update(FickianDiffusionSettings()) 55 | self.name = name 56 | -------------------------------------------------------------------------------- /openpnm/algorithms/_fourier_conduction.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from openpnm.algorithms import ReactiveTransport 3 | from openpnm.utils import Docorator 4 | 5 | 6 | logger = logging.getLogger(__name__) 7 | docstr = Docorator() 8 | 9 | 10 | __all__ = ['FourierConduction'] 11 | 12 | 13 | @docstr.dedent 14 | class FourierConductionSettings: 15 | r''' 16 | 17 | Parameters 18 | ---------- 19 | %(ReactiveTransportSettings.parameters)s 20 | 21 | ''' 22 | quantity = 'pore.temperature' 23 | conductance = 'throat.thermal_conductance' 24 | 25 | 26 | class FourierConduction(ReactiveTransport): 27 | r""" 28 | A subclass of LinearTransport to simulate heat conduction 29 | 30 | """ 31 | 32 | def __init__(self, name='fourier_?', **kwargs): 33 | super().__init__(name=name, **kwargs) 34 | self.settings._update(FourierConductionSettings()) 35 | -------------------------------------------------------------------------------- /openpnm/algorithms/_ohmic_conduction.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from openpnm.algorithms import ReactiveTransport 3 | from openpnm.utils import Docorator 4 | 5 | 6 | docstr = Docorator() 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | __all__ = ['OhmicConduction'] 11 | 12 | 13 | @docstr.dedent 14 | class OhmicConductionSettings: 15 | r""" 16 | 17 | Parameters 18 | ---------- 19 | %(ReactiveTransportSettings.parameters)s 20 | 21 | """ 22 | quantity = 'pore.voltage' 23 | conductance = 'throat.electrical_conductance' 24 | 25 | 26 | class OhmicConduction(ReactiveTransport): 27 | r""" 28 | A subclass of LinearTransport to simulate electron and ionic 29 | conduction. The 2 main roles of this subclass are to set the default 30 | property names and to implement a method for calculating the effective 31 | conductivity of the network. 32 | 33 | """ 34 | 35 | def __init__(self, name='ohmic_?', **kwargs): 36 | super().__init__(name=name, **kwargs) 37 | self.settings._update(OhmicConductionSettings()) 38 | -------------------------------------------------------------------------------- /openpnm/algorithms/_stokes_flow.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from openpnm.algorithms import ReactiveTransport 3 | from openpnm.utils import Docorator 4 | 5 | 6 | __all__ = ['StokesFlow'] 7 | 8 | 9 | logger = logging.getLogger(__name__) 10 | docstr = Docorator() 11 | 12 | 13 | @docstr.dedent 14 | class StokesFlowSettings: 15 | r''' 16 | 17 | Parameters 18 | ---------- 19 | %(ReactiveTransportSettings.parameters)s 20 | ''' 21 | quantity = 'pore.pressure' 22 | conductance = 'throat.hydraulic_conductance' 23 | 24 | 25 | class StokesFlow(ReactiveTransport): 26 | r""" 27 | A subclass of LinearTransport to simulate viscous flow. 28 | """ 29 | 30 | def __init__(self, name='stokes_?', **kwargs): 31 | super().__init__(name=name, **kwargs) 32 | self.settings._update(StokesFlowSettings()) 33 | -------------------------------------------------------------------------------- /openpnm/algorithms/_transient_advection_diffusion.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from openpnm.algorithms import TransientReactiveTransport, AdvectionDiffusion 3 | from openpnm.utils import Docorator 4 | 5 | 6 | __all__ = ['TransientAdvectionDiffusion'] 7 | 8 | 9 | docstr = Docorator() 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | @docstr.dedent 14 | class TransientAdvectionDiffusionSettings: 15 | r""" 16 | Parameters 17 | ---------- 18 | %(AdvectionDiffusionSettings.parameters)s 19 | %(TransientReactiveTransportSettings.parameters)s 20 | 21 | """ 22 | 23 | 24 | class TransientAdvectionDiffusion(TransientReactiveTransport, 25 | AdvectionDiffusion): 26 | r""" 27 | A subclass of Transport to perform steady and transient simulations 28 | of pure diffusion and advection-diffusion problems. 29 | """ 30 | 31 | def __init__(self, name='trans_ad_?', **kwargs): 32 | super().__init__(name=name, **kwargs) 33 | self.settings._update(TransientAdvectionDiffusionSettings()) 34 | -------------------------------------------------------------------------------- /openpnm/algorithms/_transient_fickian_diffusion.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from openpnm.algorithms import TransientReactiveTransport, FickianDiffusion 3 | 4 | 5 | __all__ = ['TransientFickianDiffusion'] 6 | 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | class TransientFickianDiffusion(TransientReactiveTransport, FickianDiffusion): 12 | r""" 13 | A class to simulate transient diffusion with reactions 14 | """ 15 | 16 | def __init__(self, name='trans_fick_?', **kwargs): 17 | super().__init__(name=name, **kwargs) 18 | -------------------------------------------------------------------------------- /openpnm/algorithms/_transient_fourier_conduction.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from openpnm.algorithms import TransientReactiveTransport, FourierConduction 3 | 4 | 5 | __all__ = ['TransientFourierConduction'] 6 | 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | class TransientFourierConductionSettings(): 12 | r""" 13 | 14 | Parameters 15 | ---------- 16 | %(TransientFourierConduction.parameters)s 17 | 18 | """ 19 | quantity = 'pore.temperature' 20 | conductance = 'throat.thermal_conductance' 21 | pore_volume = 'pore.volume' 22 | 23 | 24 | class TransientFourierConduction(TransientReactiveTransport, FourierConduction): 25 | r""" 26 | A class to simulate transient thermal diffusion with heat source 27 | """ 28 | 29 | def __init__(self, name='trans_fourier_?', **kwargs): 30 | super().__init__(name=name, **kwargs) 31 | -------------------------------------------------------------------------------- /openpnm/beta/__init__.py: -------------------------------------------------------------------------------- 1 | from ._funcs import * 2 | -------------------------------------------------------------------------------- /openpnm/beta/_funcs.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | __all__ = [ 5 | 'relax_points', 6 | ] 7 | 8 | 9 | def relax_points(network, iterations=3, f=0.5): 10 | r""" 11 | Performs a relaxation of Delaunay points based on distance weighted average 12 | of connected Voronoi points 13 | 14 | Parameters 15 | ---------- 16 | network : dict 17 | OpenPNM network dictionary 18 | iterations : int 19 | The number of time to iteratively apply the relaxation 20 | f : scalar 21 | A damping factor to limit the how much the point actually moves towards 22 | new computed point, to prevent overshooting. 23 | 24 | Returns 25 | ------- 26 | coords : ndarray 27 | The Np-by-3 array of pore coordinates after relaxation. 28 | """ 29 | if isinstance(f, int): 30 | f = [f]*iterations 31 | Ts = network.throats('interconnect') 32 | Ps = network.pores('delaunay') 33 | conns = network.conns[Ts] 34 | mask1 = network['pore.delaunay'][conns[:, 0]] 35 | mask2 = network['pore.delaunay'][conns[:, 1]] 36 | coords = np.copy(network.coords) 37 | for i in range(iterations): 38 | new_coords = np.zeros((len(Ps), 3), dtype=float) 39 | d = np.sqrt(np.sum((coords[conns[:, 0]] - coords[conns[:, 1]])**2, axis=1)) 40 | weights = np.zeros(len(Ps), dtype=float) 41 | for ax in range(coords.shape[1]): 42 | np.add.at(new_coords[:, ax], 43 | conns[:, 0][mask1], 44 | d[mask1]*coords[conns[:, 1][mask1], ax]) 45 | np.add.at(new_coords[:, ax], 46 | conns[:, 1][mask2], 47 | d[mask2]*coords[conns[:, 0][mask2], ax]) 48 | np.add.at(weights, conns[:, 0][mask1], d[mask1]) 49 | np.add.at(weights, conns[:, 1][mask2], d[mask2]) 50 | delta = ((new_coords.T/weights).T - coords[Ps, :])*f[i] 51 | coords[Ps, :] += delta 52 | return coords 53 | -------------------------------------------------------------------------------- /openpnm/contrib/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Contrib 3 | ------- 4 | 5 | This module contains contributions to OpenPNM, both from the developers as 6 | well as users that are not ready to be officially included but are worth 7 | sharing. These are subject to api changes, being moved, and potentially 8 | removed. 9 | 10 | """ 11 | 12 | from ._multiphase import * 13 | from ._transient_multiphysics import * 14 | -------------------------------------------------------------------------------- /openpnm/core/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Main classes of OpenPNM 3 | ======================= 4 | 5 | This module contains the main classes from which all other major objects derive. 6 | 7 | The Base class 8 | -------------- 9 | 10 | The ``Base`` class is a ``dict`` that has added methods for indexing the pores 11 | and throats, applying labels, and managing the stored data. All OpenPNM 12 | object inherit from ``Base`` so possess these methods. 13 | 14 | """ 15 | 16 | from ._models import * 17 | from ._mixins import * 18 | from ._base2 import * 19 | -------------------------------------------------------------------------------- /openpnm/integrators/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Collection of ODE integrators for transient simulations 3 | ======================================================= 4 | 5 | The ``integrators`` module contains wrapper classes for ode solvers. 6 | 7 | """ 8 | 9 | from ._base import * 10 | from ._scipy import * 11 | -------------------------------------------------------------------------------- /openpnm/integrators/_base.py: -------------------------------------------------------------------------------- 1 | __all__ = ['Integrator'] 2 | 3 | 4 | class Integrator: 5 | """Base class for integrators.""" 6 | ... 7 | -------------------------------------------------------------------------------- /openpnm/integrators/_scipy.py: -------------------------------------------------------------------------------- 1 | from scipy.integrate import solve_ivp 2 | 3 | from openpnm.algorithms._solution import TransientSolution 4 | from openpnm.integrators import Integrator 5 | 6 | __all__ = ['ScipyRK45'] 7 | 8 | 9 | class ScipyRK45(Integrator): 10 | """Integrator class based on SciPy's implementation of RK45""" 11 | 12 | def __init__(self, atol=1e-6, rtol=1e-6, verbose=False, linsolver=None): 13 | self.atol = atol 14 | self.rtol = rtol 15 | self.verbose = verbose 16 | self.linsolver = linsolver 17 | 18 | def solve(self, rhs, x0, tspan, saveat, **kwargs): 19 | """ 20 | Solves the system of ODEs defined by dy/dt = rhs(t, y). 21 | 22 | Parameters 23 | ---------- 24 | rhs : function handle 25 | RHS vector in the system of ODEs defined by dy/dt = rhs(t, y) 26 | x0 : array_like 27 | Initial value for the system of ODEs 28 | tspan : array_like 29 | 2-element tuple (or array) representing the timespan for the 30 | system of ODEs 31 | saveat : float or array_like 32 | If float, defines the time interval at which the solution is 33 | to be stored. If array_like, defines the time points at which 34 | the solution is to be stored. 35 | **kwargs : keyword arguments 36 | Other keyword arguments that might get used by the integrator 37 | 38 | Returns 39 | ------- 40 | TransientSolution 41 | Solution of the system of ODEs stored in a subclass of numpy's 42 | ndarray with some added functionalities (ex. you can get the 43 | solution at intermediate time points via: y = soln(t_i)). 44 | 45 | """ 46 | options = { 47 | "atol": self.atol, 48 | "rtol": self.rtol, 49 | "t_eval": saveat, 50 | # FIXME: uncomment next line when/if scipy#11815 is merged 51 | # "verbose": self.verbose, 52 | } 53 | sol = solve_ivp(rhs, tspan, x0, method="RK45", **options) 54 | if sol.success: 55 | return TransientSolution(sol.t, sol.y) 56 | raise Exception(sol.message) 57 | -------------------------------------------------------------------------------- /openpnm/io/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Collection of functions for import/export of data 3 | ================================================== 4 | 5 | This module contains functionality for importing and exporting data 6 | between OpenPNM and other formats. 7 | 8 | """ 9 | 10 | from ._utils import * 11 | from ._dict import project_to_dict 12 | from ._vtk import project_to_vtk 13 | from ._pandas import project_to_pandas, network_to_pandas 14 | from ._csv import project_to_csv, network_to_csv, network_from_csv 15 | from ._hdf5 import project_to_hdf5, print_hdf5 16 | from ._xdmf import project_to_xdmf 17 | from ._marock import network_from_marock 18 | from ._porespy import network_from_porespy 19 | from ._paraview import project_to_paraview 20 | from ._networkx import network_to_networkx, network_from_networkx 21 | from ._statoil import network_from_statoil 22 | from ._pergeos import network_to_pergeos, network_from_pergeos 23 | from ._jsongraph import network_to_jsongraph, network_from_jsongraph 24 | from ._stl import network_to_stl 25 | from ._comsol import network_to_comsol 26 | from ._salome import network_to_salome 27 | -------------------------------------------------------------------------------- /openpnm/io/_porespy.py: -------------------------------------------------------------------------------- 1 | import pickle as pk 2 | from openpnm.network import Network 3 | from openpnm.io import _parse_filename 4 | 5 | 6 | def network_from_porespy(filename): 7 | r""" 8 | Load a network extracted using the PoreSpy package 9 | 10 | Parameters 11 | ---------- 12 | filename : str or dict 13 | Can either be a filename pointing to a pickled dictionary, or a 14 | handle to a dictionary in memory. The second option lets users 15 | avoid the step of saving the dictionary to a file. 16 | 17 | Returns 18 | ------- 19 | network : dict 20 | An OpenPNM network dictionary 21 | 22 | """ 23 | # Parse the filename 24 | if isinstance(filename, dict): 25 | net = filename 26 | else: 27 | filename = _parse_filename(filename=filename) 28 | with open(filename, mode='rb') as f: 29 | net = pk.load(f) 30 | 31 | network = Network() 32 | network.update(net) 33 | 34 | return network 35 | -------------------------------------------------------------------------------- /openpnm/io/_utils.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import numpy as np 3 | from pathlib import Path 4 | 5 | 6 | __all__ = [ 7 | '_parse_filename', 8 | '_parse_args', 9 | ] 10 | 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | def _parse_filename(filename, ext=""): 16 | p = Path(filename) 17 | p = p.resolve() 18 | if ext == "": 19 | ext = p.suffix 20 | # If extension not part of filename 21 | ext = "." + (ext.strip(".")) 22 | if p.suffix != ext: 23 | p = p.with_suffix(ext) 24 | return p 25 | 26 | 27 | def _parse_args(network, phases): 28 | try: 29 | project = network.project 30 | network = [network] 31 | except AttributeError: 32 | project = network[0].project 33 | # Ensure phases is a list, even if empty 34 | if not isinstance(phases, list): 35 | phases = [phases] 36 | return (project, network, phases) 37 | -------------------------------------------------------------------------------- /openpnm/models/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Collection of pore-scale models 3 | =============================== 4 | 5 | """ 6 | 7 | from ._doctxt import * 8 | from . import misc 9 | from . import network 10 | from . import geometry 11 | from . import phase 12 | from . import physics 13 | from . import collections 14 | -------------------------------------------------------------------------------- /openpnm/models/_doctxt.py: -------------------------------------------------------------------------------- 1 | try: 2 | from matplotlib._docstring import Substitution 3 | except ModuleNotFoundError: 4 | from matplotlib.docstring import Substitution 5 | 6 | 7 | 8 | __all__ = [ 9 | '_doctxt', 10 | ] 11 | 12 | 13 | _doctxt = Substitution( 14 | phase= 15 | r"""phase : OpenPNM Phase object 16 | The phase object to which this model is associated (i.e. attached). 17 | This controls the length of the calculated array(s), and also 18 | provides access to other necessary properties.""", 19 | network= 20 | r"""network : OpenPNM Network object 21 | The network object to which this model is associated (i.e. 22 | attached). This controls the length of the calculated array(s), 23 | and also provides access to other necessary properties.""", 24 | dict_blurb= 25 | r"""Name of the dictionary key on the target object pointing to the 26 | array containing values of """, 27 | return_arr= 28 | r"""values : ndarray 29 | A numpy ndarray containing the computed values of """, 30 | ) 31 | -------------------------------------------------------------------------------- /openpnm/models/collections/__init__.py: -------------------------------------------------------------------------------- 1 | from . import geometry 2 | from . import physics 3 | from . import phase 4 | from . import network 5 | -------------------------------------------------------------------------------- /openpnm/models/collections/geometry/__init__.py: -------------------------------------------------------------------------------- 1 | from .spheres_and_cylinders import spheres_and_cylinders 2 | from .circles_and_rectangles import circles_and_rectangles 3 | from .cones_and_cylinders import cones_and_cylinders 4 | from .pyramids_and_cuboids import pyramids_and_cuboids 5 | from .trapezoids_and_rectangles import trapezoids_and_rectangles 6 | from .cubes_and_cuboids import cubes_and_cuboids 7 | from .squares_and_rectangles import squares_and_rectangles 8 | -------------------------------------------------------------------------------- /openpnm/models/collections/geometry/circles_and_rectangles.py: -------------------------------------------------------------------------------- 1 | import openpnm.models as mods 2 | 3 | 4 | circles_and_rectangles = { 5 | 'pore.seed': { 6 | 'model': mods.misc.random, 7 | 'element': 'pore', 8 | 'num_range': [0.2, 0.7], 9 | 'seed': None, 10 | }, 11 | 'pore.max_size': { 12 | 'model': mods.geometry.pore_size.largest_sphere, 13 | 'iters': 10, 14 | }, 15 | 'pore.diameter': { 16 | 'model': mods.misc.product, 17 | 'props': ['pore.max_size', 'pore.seed'], 18 | }, 19 | 'pore.volume': { 20 | 'model': mods.geometry.pore_volume.circle, 21 | 'pore_diameter': 'pore.diameter', 22 | }, 23 | 'throat.max_size': { 24 | 'model': mods.misc.from_neighbor_pores, 25 | 'mode': 'min', 26 | 'prop': 'pore.diameter', 27 | }, 28 | 'throat.diameter': { 29 | 'model': mods.misc.scaled, 30 | 'factor': 0.5, 31 | 'prop': 'throat.max_size', 32 | }, 33 | 'throat.length': { 34 | 'model': mods.geometry.throat_length.circles_and_rectangles, 35 | 'pore_diameter': 'pore.diameter', 36 | 'throat_diameter': 'throat.diameter', 37 | }, 38 | 'throat.volume': { 39 | 'model': mods.geometry.throat_volume.rectangle, 40 | 'throat_diameter': 'throat.diameter', 41 | 'throat_length': 'throat.length', 42 | }, 43 | 'throat.cross_sectional_area': { 44 | 'model': mods.geometry.throat_cross_sectional_area.rectangle, 45 | 'throat_diameter': 'throat.diameter', 46 | }, 47 | 'throat.diffusive_size_factors': { 48 | 'model': mods.geometry.diffusive_size_factors.circles_and_rectangles, 49 | 'pore_diameter': 'pore.diameter', 50 | 'throat_diameter': 'throat.diameter', 51 | }, 52 | 'throat.hydraulic_size_factors': { 53 | 'model': mods.geometry.hydraulic_size_factors.circles_and_rectangles, 54 | 'pore_diameter': 'pore.diameter', 55 | 'throat_diameter': 'throat.diameter', 56 | }, 57 | } 58 | -------------------------------------------------------------------------------- /openpnm/models/collections/geometry/cones_and_cylinders.py: -------------------------------------------------------------------------------- 1 | import openpnm.models as mods 2 | 3 | 4 | cones_and_cylinders = { 5 | 'pore.seed': { 6 | 'model': mods.misc.random, 7 | 'element': 'pore', 8 | 'num_range': [0.2, 0.7], 9 | 'seed': None, 10 | }, 11 | 'pore.max_size': { 12 | 'model': mods.geometry.pore_size.largest_sphere, 13 | 'iters': 10, 14 | }, 15 | 'pore.diameter': { 16 | 'model': mods.misc.product, 17 | 'props': ['pore.max_size', 'pore.seed'], 18 | }, 19 | 'pore.volume': { 20 | 'model': mods.geometry.pore_volume.sphere, 21 | 'pore_diameter': 'pore.diameter', 22 | }, 23 | 'throat.max_size': { 24 | 'model': mods.misc.from_neighbor_pores, 25 | 'mode': 'min', 26 | 'prop': 'pore.diameter', 27 | }, 28 | 'throat.diameter': { 29 | 'model': mods.misc.scaled, 30 | 'factor': 0.5, 31 | 'prop': 'throat.max_size', 32 | }, 33 | 'throat.length': { 34 | 'model': mods.geometry.throat_length.cones_and_cylinders, 35 | 'pore_diameter': 'pore.diameter', 36 | 'throat_diameter': 'throat.diameter', 37 | }, 38 | 'throat.volume': { 39 | 'model': mods.geometry.throat_volume.cylinder, 40 | 'throat_diameter': 'throat.diameter', 41 | 'throat_length': 'throat.length', 42 | }, 43 | 'throat.cross_sectional_area': { 44 | 'model': mods.geometry.throat_cross_sectional_area.cylinder, 45 | 'throat_diameter': 'throat.diameter', 46 | }, 47 | 'throat.diffusive_size_factors': { 48 | 'model': mods.geometry.diffusive_size_factors.cones_and_cylinders, 49 | 'pore_diameter': 'pore.diameter', 50 | 'throat_diameter': 'throat.diameter', 51 | }, 52 | 'throat.hydraulic_size_factors': { 53 | 'model': mods.geometry.hydraulic_size_factors.cones_and_cylinders, 54 | 'pore_diameter': 'pore.diameter', 55 | 'throat_diameter': 'throat.diameter', 56 | }, 57 | } 58 | -------------------------------------------------------------------------------- /openpnm/models/collections/geometry/cubes_and_cuboids.py: -------------------------------------------------------------------------------- 1 | import openpnm.models as mods 2 | 3 | 4 | cubes_and_cuboids = { 5 | 'pore.seed': { 6 | 'model': mods.misc.random, 7 | 'element': 'pore', 8 | 'num_range': [0.2, 0.7], 9 | 'seed': None, 10 | }, 11 | 'pore.max_size': { 12 | 'model': mods.geometry.pore_size.largest_sphere, 13 | 'iters': 10, 14 | }, 15 | 'pore.diameter': { 16 | 'model': mods.misc.product, 17 | 'props': ['pore.max_size', 'pore.seed'], 18 | }, 19 | 'pore.volume': { 20 | 'model': mods.geometry.pore_volume.cube, 21 | 'pore_diameter': 'pore.diameter', 22 | }, 23 | 'throat.max_size': { 24 | 'model': mods.misc.from_neighbor_pores, 25 | 'mode': 'min', 26 | 'prop': 'pore.diameter', 27 | }, 28 | 'throat.diameter': { 29 | 'model': mods.misc.scaled, 30 | 'factor': 0.5, 31 | 'prop': 'throat.max_size', 32 | }, 33 | 'throat.length': { 34 | 'model': mods.geometry.throat_length.cubes_and_cuboids, 35 | 'pore_diameter': 'pore.diameter', 36 | 'throat_diameter': 'throat.diameter', 37 | }, 38 | 'throat.cross_sectional_area': { 39 | 'model': mods.geometry.throat_cross_sectional_area.cuboid, 40 | 'throat_diameter': 'throat.diameter', 41 | }, 42 | 'throat.volume': { 43 | 'model': mods.geometry.throat_volume.cuboid, 44 | 'throat_diameter': 'throat.diameter', 45 | 'throat_length': 'throat.length', 46 | }, 47 | 'throat.diffusive_size_factors': { 48 | 'model': mods.geometry.diffusive_size_factors.cubes_and_cuboids, 49 | 'pore_diameter': 'pore.diameter', 50 | 'throat_diameter': 'throat.diameter', 51 | }, 52 | 'throat.hydraulic_size_factors': { 53 | 'model': mods.geometry.hydraulic_size_factors.cubes_and_cuboids, 54 | 'pore_diameter': 'pore.diameter', 55 | 'throat_diameter': 'throat.diameter', 56 | }, 57 | } 58 | -------------------------------------------------------------------------------- /openpnm/models/collections/geometry/pyramids_and_cuboids.py: -------------------------------------------------------------------------------- 1 | import openpnm.models as mods 2 | 3 | 4 | pyramids_and_cuboids = { 5 | 'pore.seed': { 6 | 'model': mods.misc.random, 7 | 'element': 'pore', 8 | 'num_range': [0.2, 0.7], 9 | 'seed': None, 10 | }, 11 | 'pore.max_size': { 12 | 'model': mods.geometry.pore_size.largest_sphere, 13 | 'iters': 10, 14 | }, 15 | 'pore.diameter': { 16 | 'model': mods.misc.product, 17 | 'props': ['pore.max_size', 'pore.seed'], 18 | }, 19 | 'pore.volume': { 20 | 'model': mods.geometry.pore_volume.sphere, 21 | 'pore_diameter': 'pore.diameter', 22 | }, 23 | 'throat.max_size': { 24 | 'model': mods.misc.from_neighbor_pores, 25 | 'mode': 'min', 26 | 'prop': 'pore.diameter', 27 | }, 28 | 'throat.diameter': { 29 | 'model': mods.misc.scaled, 30 | 'factor': 0.5, 31 | 'prop': 'throat.max_size', 32 | }, 33 | 'throat.length': { 34 | 'model': mods.geometry.throat_length.pyramids_and_cuboids, 35 | 'pore_diameter': 'pore.diameter', 36 | 'throat_diameter': 'throat.diameter', 37 | }, 38 | 'throat.cross_sectional_area': { 39 | 'model': mods.geometry.throat_cross_sectional_area.cuboid, 40 | 'throat_diameter': 'throat.diameter', 41 | }, 42 | 'throat.volume': { 43 | 'model': mods.geometry.throat_volume.cuboid, 44 | 'throat_diameter': 'throat.diameter', 45 | 'throat_length': 'throat.length', 46 | }, 47 | 'throat.diffusive_size_factors': { 48 | 'model': mods.geometry.diffusive_size_factors.pyramids_and_cuboids, 49 | 'pore_diameter': 'pore.diameter', 50 | 'throat_diameter': 'throat.diameter', 51 | }, 52 | 'throat.hydraulic_size_factors': { 53 | 'model': mods.geometry.hydraulic_size_factors.pyramids_and_cuboids, 54 | 'pore_diameter': 'pore.diameter', 55 | 'throat_diameter': 'throat.diameter', 56 | }, 57 | } 58 | -------------------------------------------------------------------------------- /openpnm/models/collections/geometry/squares_and_rectangles.py: -------------------------------------------------------------------------------- 1 | import openpnm.models as mods 2 | 3 | 4 | squares_and_rectangles = { 5 | 'pore.seed': { 6 | 'model': mods.misc.random, 7 | 'element': 'pore', 8 | 'num_range': [0.2, 0.7], 9 | 'seed': None, 10 | }, 11 | 'pore.max_size': { 12 | 'model': mods.geometry.pore_size.largest_sphere, 13 | 'iters': 10, 14 | }, 15 | 'pore.diameter': { 16 | 'model': mods.misc.product, 17 | 'props': ['pore.max_size', 'pore.seed'], 18 | }, 19 | 'pore.volume': { 20 | 'model': mods.geometry.pore_volume.square, 21 | 'pore_diameter': 'pore.diameter', 22 | }, 23 | 'throat.max_size': { 24 | 'model': mods.misc.from_neighbor_pores, 25 | 'mode': 'min', 26 | 'prop': 'pore.diameter', 27 | }, 28 | 'throat.diameter': { 29 | 'model': mods.misc.scaled, 30 | 'factor': 0.5, 31 | 'prop': 'throat.max_size', 32 | }, 33 | 'throat.length': { 34 | 'model': mods.geometry.throat_length.squares_and_rectangles, 35 | 'pore_diameter': 'pore.diameter', 36 | 'throat_diameter': 'throat.diameter', 37 | }, 38 | 'throat.cross_sectional_area': { 39 | 'model': mods.geometry.throat_cross_sectional_area.rectangle, 40 | 'throat_diameter': 'throat.diameter', 41 | }, 42 | 'throat.volume': { 43 | 'model': mods.geometry.throat_volume.rectangle, 44 | 'throat_diameter': 'throat.diameter', 45 | 'throat_length': 'throat.length', 46 | }, 47 | 'throat.diffusive_size_factors': { 48 | 'model': mods.geometry.diffusive_size_factors.squares_and_rectangles, 49 | 'pore_diameter': 'pore.diameter', 50 | 'throat_diameter': 'throat.diameter', 51 | }, 52 | 'throat.hydraulic_size_factors': { 53 | 'model': mods.geometry.hydraulic_size_factors.squares_and_rectangles, 54 | 'pore_diameter': 'pore.diameter', 55 | 'throat_diameter': 'throat.diameter', 56 | }, 57 | } 58 | -------------------------------------------------------------------------------- /openpnm/models/collections/geometry/trapezoids_and_rectangles.py: -------------------------------------------------------------------------------- 1 | import openpnm.models as mods 2 | 3 | 4 | trapezoids_and_rectangles = { 5 | 'pore.seed': { 6 | 'model': mods.misc.random, 7 | 'element': 'pore', 8 | 'num_range': [0.2, 0.7], 9 | 'seed': None, 10 | }, 11 | 'pore.max_size': { 12 | 'model': mods.geometry.pore_size.largest_sphere, 13 | 'iters': 10, 14 | }, 15 | 'pore.diameter': { 16 | 'model': mods.misc.product, 17 | 'props': ['pore.max_size', 'pore.seed'], 18 | }, 19 | 'pore.volume': { 20 | 'model': mods.geometry.pore_volume.circle, 21 | 'pore_diameter': 'pore.diameter', 22 | }, 23 | 'throat.max_size': { 24 | 'model': mods.misc.from_neighbor_pores, 25 | 'mode': 'min', 26 | 'prop': 'pore.diameter', 27 | }, 28 | 'throat.diameter': { 29 | 'model': mods.misc.scaled, 30 | 'factor': 0.5, 31 | 'prop': 'throat.max_size', 32 | }, 33 | 'throat.length': { 34 | 'model': mods.geometry.throat_length.trapezoids_and_rectangles, 35 | 'pore_diameter': 'pore.diameter', 36 | 'throat_diameter': 'throat.diameter', 37 | }, 38 | 'throat.volume': { 39 | 'model': mods.geometry.throat_volume.rectangle, 40 | 'throat_diameter': 'throat.diameter', 41 | 'throat_length': 'throat.length', 42 | }, 43 | 'throat.cross_sectional_area': { 44 | 'model': mods.geometry.throat_cross_sectional_area.rectangle, 45 | 'throat_diameter': 'throat.diameter', 46 | }, 47 | 'throat.diffusive_size_factors': { 48 | 'model': mods.geometry.diffusive_size_factors.trapezoids_and_rectangles, 49 | 'pore_diameter': 'pore.diameter', 50 | 'throat_diameter': 'throat.diameter', 51 | }, 52 | 'throat.hydraulic_size_factors': { 53 | 'model': mods.geometry.hydraulic_size_factors.trapezoids_and_rectangles, 54 | 'pore_diameter': 'pore.diameter', 55 | 'throat_diameter': 'throat.diameter', 56 | }, 57 | } 58 | -------------------------------------------------------------------------------- /openpnm/models/collections/network/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | 3 | Network Models Collections 4 | -------------------------- 5 | 6 | """ 7 | -------------------------------------------------------------------------------- /openpnm/models/collections/phase/__init__.py: -------------------------------------------------------------------------------- 1 | from .air import air 2 | from .water import water 3 | from .mercury import mercury 4 | from .liquid import standard_liquid_mixture, standard_liquid 5 | from .gas import standard_gas_mixture, standard_gas, binary_gas_mixture 6 | -------------------------------------------------------------------------------- /openpnm/models/collections/phase/air.py: -------------------------------------------------------------------------------- 1 | import openpnm.models as mods 2 | 3 | 4 | air = { 5 | 'pore.density': { 6 | 'model': mods.phase.density.ideal_gas, 7 | }, 8 | 'pore.molar_density': { 9 | 'model': mods.phase.density.mass_to_molar, 10 | }, 11 | 'pore.diffusivity': { 12 | 'model': mods.phase.diffusivity.gas_mixture_fesg, 13 | 'MWs': [31.9988, 28.0134], 14 | 'Vdms': [16.6, 17.9], 15 | }, 16 | 'pore.thermal_conductivity': { 17 | 'model': mods.misc.polynomial, 18 | 'prop': 'pore.temperature', 19 | 'a': [0.00422791, 0.0000789606, -1.56383E-08], 20 | }, 21 | 'pore.viscosity': { 22 | 'model': mods.misc.polynomial, 23 | 'prop': 'pore.temperature', 24 | 'a': [0.00000182082, 6.51815E-08, -3.48553E-11, 1.11409E-14], 25 | }, 26 | } 27 | -------------------------------------------------------------------------------- /openpnm/models/collections/phase/gas.py: -------------------------------------------------------------------------------- 1 | import openpnm.models as mods 2 | 3 | 4 | standard_gas = { 5 | 'pore.density': { 6 | 'model': mods.phase.density.ideal_gas, 7 | }, 8 | 'pore.heat_capacity_gas': { 9 | 'model': mods.phase.heat_capacity.gas_pure_TRC, 10 | }, 11 | 'pore.heat_capacity': { 12 | 'model': mods.phase.heat_capacity.gas_pure_TRC, 13 | }, 14 | 'pore.thermal_conductivity': { 15 | 'model': mods.phase.thermal_conductivity.gas_pure_gismr, 16 | }, 17 | 'pore.viscosity': { 18 | 'model': mods.phase.viscosity.gas_pure_gesmr, 19 | }, 20 | } 21 | 22 | 23 | standard_gas_mixture = { 24 | 'pore.density': { 25 | 'model': mods.phase.density.ideal_gas, 26 | }, 27 | 'pore.heat_capacity': { 28 | 'model': mods.phase.heat_capacity.gas_mixture_yweighted, 29 | }, 30 | 'pore.thermal_conductivity': { 31 | 'model': mods.phase.thermal_conductivity.gas_mixture_whz, 32 | }, 33 | 'pore.viscosity': { 34 | 'model': mods.phase.viscosity.gas_mixture_hz, 35 | }, 36 | } 37 | 38 | binary_gas_mixture = { 39 | 'pore.density': { 40 | 'model': mods.phase.density.ideal_gas, 41 | }, 42 | 'pore.heat_capacity': { 43 | 'model': mods.phase.heat_capacity.gas_mixture_yweighted, 44 | }, 45 | 'pore.thermal_conductivity': { 46 | 'model': mods.phase.thermal_conductivity.gas_mixture_whz, 47 | }, 48 | 'pore.viscosity': { 49 | 'model': mods.phase.viscosity.gas_mixture_hz, 50 | }, 51 | 'pore.diffusivity': { 52 | 'model': mods.phase.diffusivity.gas_mixture_ce, 53 | }, 54 | } 55 | -------------------------------------------------------------------------------- /openpnm/models/collections/phase/liquid.py: -------------------------------------------------------------------------------- 1 | import openpnm.models as mods 2 | 3 | 4 | standard_liquid = { 5 | 'pore.density': { 6 | 'model': mods.phase.density.liquid_pure_COSTALD, 7 | }, 8 | 'pore.heat_capacity_gas': { 9 | 'model': mods.phase.heat_capacity.gas_pure_TRC, 10 | }, 11 | 'pore.heat_capacity': { 12 | 'model': mods.phase.heat_capacity.liquid_pure_rp, 13 | }, 14 | 'pore.thermal_conductivity': { 15 | 'model': mods.phase.thermal_conductivity.liquid_pure_gismr, 16 | }, 17 | 'pore.viscosity': { 18 | 'model': mods.phase.viscosity.liquid_pure_ls, 19 | }, 20 | 'pore.vapor_pressure': { 21 | 'model': mods.phase.vapor_pressure.liquid_pure_antoine, 22 | }, 23 | } 24 | 25 | 26 | standard_liquid_mixture = { 27 | 'pore.density': { 28 | 'model': mods.phase.density.liquid_mixture_COSTALD, 29 | }, 30 | 'pore.heat_capacity': { 31 | 'model': mods.phase.heat_capacity.liquid_mixture_xweighted, 32 | }, 33 | 'pore.thermal_conductivity': { 34 | 'model': mods.phase.thermal_conductivity.liquid_mixture_DIPPR9H, 35 | }, 36 | 'pore.viscosity': { 37 | 'model': mods.phase.viscosity.liquid_mixture_xweighted, 38 | }, 39 | } 40 | -------------------------------------------------------------------------------- /openpnm/models/collections/phase/mercury.py: -------------------------------------------------------------------------------- 1 | import openpnm.models as mods 2 | 3 | 4 | mercury = { 5 | 'throat.contact_angle': { 6 | 'model': mods.misc.constant, 7 | 'value': 140, 8 | }, 9 | 'pore.density': { 10 | 'model': mods.misc.linear, 11 | 'prop': 'pore.temperature', 12 | 'b': 14280.9, 13 | 'm': -2.47004, 14 | }, 15 | 'pore.molar_density': { 16 | 'model': mods.phase.density.mass_to_molar, 17 | }, 18 | 'pore.surface_tension': { 19 | 'model': mods.misc.linear, 20 | 'prop': 'pore.temperature', 21 | 'b': 0.56254, 22 | 'm': -0.00028, 23 | }, 24 | 'pore.thermal_conductivity': { 25 | 'model': mods.misc.polynomial, 26 | 'prop': 'pore.temperature', 27 | 'a': [3.98691, 0.0170967, -0.0000063862], 28 | }, 29 | 'pore.viscosity': { 30 | 'model': mods.misc.polynomial, 31 | 'prop': 'pore.temperature', 32 | 'a': [0.00355837, -0.0000100131, 1.23684E-08, -5.1684E-12], 33 | }, 34 | } 35 | 36 | # Thermophysical Properties of Materials for Nuclear Engineering: 37 | # IAEA, Vienna, 2008. ISBN 978-92-0-106508-7: 38 | -------------------------------------------------------------------------------- /openpnm/models/collections/phase/water.py: -------------------------------------------------------------------------------- 1 | import openpnm.models as mods 2 | 3 | 4 | water = { 5 | 'pore.contact_angle': { 6 | 'model': mods.misc.constant, 7 | 'value': 110.0, 8 | }, 9 | 'pore.density': { 10 | 'model': mods.phase.density.water_correlation, 11 | }, 12 | 'pore.molar_density': { 13 | 'model': mods.phase.density.mass_to_molar, 14 | }, 15 | 'pore.surface_tension': { 16 | 'model': mods.phase.surface_tension.water_correlation, 17 | }, 18 | 'pore.thermal_conductivity': { 19 | 'model': mods.phase.thermal_conductivity.water_correlation, 20 | }, 21 | 'pore.vapor_pressure': { 22 | 'model': mods.phase.vapor_pressure.liquid_pure_antoine, 23 | }, 24 | 'pore.viscosity': { 25 | 'model': mods.phase.viscosity.water_correlation, 26 | }, 27 | } 28 | -------------------------------------------------------------------------------- /openpnm/models/collections/physics/__init__.py: -------------------------------------------------------------------------------- 1 | from .basic import basic 2 | from .standard import standard 3 | -------------------------------------------------------------------------------- /openpnm/models/collections/physics/basic.py: -------------------------------------------------------------------------------- 1 | import openpnm.models.physics as mods 2 | 3 | 4 | basic = { 5 | 'throat.hydraulic_conductance': { 6 | 'model': mods.hydraulic_conductance.generic_hydraulic, 7 | }, 8 | 'throat.diffusive_conductance': { 9 | 'model': mods.diffusive_conductance.generic_diffusive, 10 | }, 11 | 'throat.entry_pressure': { 12 | 'model': mods.capillary_pressure.washburn, 13 | }, 14 | } 15 | -------------------------------------------------------------------------------- /openpnm/models/collections/physics/standard.py: -------------------------------------------------------------------------------- 1 | import openpnm.models.physics as mods 2 | 3 | 4 | standard = { 5 | 'throat.hydraulic_conductance': { 6 | 'model': mods.hydraulic_conductance.generic_hydraulic, 7 | }, 8 | 'throat.diffusive_conductance': { 9 | 'model': mods.diffusive_conductance.generic_diffusive, 10 | }, 11 | 'throat.entry_pressure': { 12 | 'model': mods.capillary_pressure.washburn, 13 | }, 14 | 'throat.thermal_conductance': { 15 | 'model': mods.thermal_conductance.generic_thermal, 16 | }, 17 | 'throat.electrical_conductance': { 18 | 'model': mods.electrical_conductance.generic_electrical, 19 | }, 20 | 'throat.ad_dif_conductance': { 21 | 'model': mods.ad_dif_conductance.ad_dif, 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /openpnm/models/geometry/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Geometry 3 | -------- 4 | 5 | This submodule contains pore-scale models that calculate geometrical 6 | properties. These models are to be added to a Geometry object. 7 | 8 | """ 9 | from ._geodocs import * 10 | from . import pore_size 11 | from . import pore_seed 12 | from . import pore_volume 13 | from . import pore_surface_area 14 | from . import pore_cross_sectional_area 15 | from . import throat_cross_sectional_area 16 | from . import throat_seed 17 | from . import throat_size 18 | from . import throat_length 19 | from . import throat_perimeter 20 | from . import throat_surface_area 21 | from . import throat_volume 22 | from . import throat_capillary_shape_factor 23 | from . import throat_centroid 24 | from . import throat_endpoints 25 | from . import throat_vector 26 | from . import hydraulic_size_factors 27 | from . import diffusive_size_factors 28 | from . import conduit_lengths 29 | -------------------------------------------------------------------------------- /openpnm/models/geometry/_geodocs.py: -------------------------------------------------------------------------------- 1 | try: 2 | from matplotlib._docstring import Substitution 3 | except ModuleNotFoundError: 4 | from matplotlib.docstring import Substitution 5 | 6 | 7 | __all__ = [ 8 | '_geodocs', 9 | ] 10 | 11 | 12 | _geodocs = Substitution( 13 | network= 14 | r"""network : OpenPNM Network object 15 | 16 | """, 17 | Dp= 18 | r"""pore_diameter : str 19 | Name of the dictionary key on ``network`` containing the ndarray of 20 | pore diameter values.""", 21 | Dt= 22 | r"""throat_diameter : str 23 | Name of the dictionary key on ``network`` containing the ndarray of 24 | throat diameter values.""", 25 | Vp= 26 | r"""pore_volume : str 27 | Name of the dictionary key on ``network`` containing the ndarray of 28 | pore volume values.""", 29 | Vt= 30 | r"""throat_volume : str 31 | Name of the dictionary key on ``network`` containing the ndarray of 32 | throat volume values.""", 33 | Lt= 34 | r"""throat_length : str 35 | Name of the dictionary key on ``network`` containing the ndarray of 36 | throat length values.""", 37 | Pcoords= 38 | r"""pore_coords : str 39 | Name of the dictionary key on ``network`` containing the ndarray of 40 | pore coordinate values.""", 41 | Tcoords= 42 | r"""throat_coords : str 43 | Name of the dictionary key on ``network`` containing the ndarray of 44 | throat centroid coordinate values.""", 45 | At= 46 | r"""throat_area : str 47 | Name of the dictionary key on ``network`` containing the ndarray of 48 | throat surface values.""", 49 | Pt= 50 | r"""throat_perimeter : str 51 | Name of the dictionary key on ``network`` containing the ndarray of 52 | throat perimeter values.""", 53 | Tcen= 54 | r"""throat_centroid : str 55 | Name of the dictionary key on ``network`` containing the ndarray of 56 | throat centroid coordinate values.""", 57 | Act= 58 | r"""throat_cross_sectional_area : str 59 | Name of the dictionary key on ``network`` containing the ndarray of 60 | throat cross-sectional area values.""", 61 | ) 62 | -------------------------------------------------------------------------------- /openpnm/models/geometry/conduit_lengths/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Conduit Lengths Models 3 | ---------------------- 4 | 5 | This model contains a selection of functions for Calculating conduit lengths 6 | in the network assuming pores and throats have different shapes. 7 | 8 | """ 9 | from ._funcs import * 10 | -------------------------------------------------------------------------------- /openpnm/models/geometry/diffusive_size_factors/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Diffusive Size Factors Models 3 | ----------------------------- 4 | 5 | This model contains a selection of functions for Computing diffusive shape 6 | coefficient for conduits assuming pores and throats have various shapes. 7 | 8 | """ 9 | 10 | from ._funcs import * 11 | -------------------------------------------------------------------------------- /openpnm/models/geometry/hydraulic_size_factors/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Hydraulic Size Factors Models 3 | ----------------------------- 4 | 5 | This model contains a selection of functions for Computing hydraulic size factors 6 | for conduits assuming pores and throats have various shapes. 7 | 8 | """ 9 | 10 | from ._funcs import * 11 | -------------------------------------------------------------------------------- /openpnm/models/geometry/pore_cross_sectional_area/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Pore Cross-sectional Area Models 3 | -------------------------------- 4 | 5 | This model contains a selection of functions for Calculating 6 | cross-sectional area assuming the pore body has various shapes. 7 | 8 | """ 9 | 10 | from ._funcs import * 11 | -------------------------------------------------------------------------------- /openpnm/models/geometry/pore_seed/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Pore Seed Models 3 | ---------------- 4 | 5 | Generates pore seeds that are spatailly correlated with their neighbors. 6 | 7 | """ 8 | 9 | from ._funcs import * 10 | -------------------------------------------------------------------------------- /openpnm/models/geometry/pore_size/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Pore Size Models 3 | ---------------- 4 | 5 | This model contains a selection of functions for finding the pore size 6 | 7 | """ 8 | 9 | from ._funcs import * 10 | -------------------------------------------------------------------------------- /openpnm/models/geometry/pore_surface_area/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Pore Surface Area Models 3 | ------------------------ 4 | 5 | This model contains a selection of functions for Calculating pore surface area. 6 | """ 7 | 8 | from ._funcs import * 9 | -------------------------------------------------------------------------------- /openpnm/models/geometry/pore_volume/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Pore Volume Models 3 | ------------------ 4 | 5 | This model contains a selection of functions for calculating pore volume 6 | from diameter assuming various pore body shape. 7 | 8 | """ 9 | 10 | from ._funcs import * 11 | -------------------------------------------------------------------------------- /openpnm/models/geometry/throat_capillary_shape_factor/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Throat Capillary Shape Factor Models 3 | ------------------------------------ 4 | 5 | This model contains a selection of functions for calculating capillary shape 6 | factor for throats. 7 | 8 | """ 9 | 10 | from ._funcs import * 11 | -------------------------------------------------------------------------------- /openpnm/models/geometry/throat_capillary_shape_factor/_funcs.py: -------------------------------------------------------------------------------- 1 | import numpy as _np 2 | from openpnm.models.geometry import _geodocs 3 | 4 | 5 | __all__ = [ 6 | "mason_morrow", 7 | "jenkins_rao", 8 | ] 9 | 10 | 11 | @_geodocs 12 | def mason_morrow( 13 | network, 14 | throat_perimeter='throat.perimeter', 15 | throat_area='throat.cross_sectional_area', 16 | ): 17 | r""" 18 | Mason and Morrow relate the capillary pressure to the shape factor in a 19 | similar way to Mortensen but for triangles. 20 | 21 | Parameters 22 | ---------- 23 | %(network)s 24 | %(Pt)s 25 | %(At)s 26 | 27 | Returns 28 | ------- 29 | 30 | References 31 | ---------- 32 | Mason, G. and Morrow, N.R.. Capillary behavior of a perfectly wetting 33 | liquid in irregular triangular tubes. Journal of Colloid and Interface 34 | Science, 141(1), pp.262-274 (1991). 35 | 36 | """ 37 | # Only apply to throats with an area 38 | ts = network.throats()[network[throat_area] <= 0] 39 | P = network[throat_perimeter] 40 | A = network[throat_area] 41 | value = A/(P**2) 42 | value[ts] = 1/(4*_np.pi) 43 | return value 44 | 45 | 46 | def jenkins_rao( 47 | network, 48 | throat_perimeter='throat.perimeter', 49 | throat_area='throat.cross_sectional_area', 50 | throat_diameter='throat.indiameter', 51 | ): 52 | r""" 53 | Jenkins and Rao relate the capillary pressure in an eliptical throat to 54 | the aspect ratio 55 | 56 | Parameters 57 | ---------- 58 | %(network)s 59 | %(Pt)s 60 | %(At)s 61 | %(Dt)s 62 | 63 | Returns 64 | ------- 65 | 66 | References 67 | ---------- 68 | Jenkins, R.G. and Rao, M.B., The effect of elliptical pores on 69 | mercury porosimetry results. Powder technology, 38(2), pp.177-180. (1984) 70 | 71 | """ 72 | P = network[throat_perimeter] 73 | A = network[throat_area] 74 | r = network[throat_diameter]/2 75 | # Normalized by value for perfect circle 76 | value = (P/A)/(2/r) 77 | return value 78 | -------------------------------------------------------------------------------- /openpnm/models/geometry/throat_centroid/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Throat Centroid Model 3 | --------------------- 4 | 5 | Calculate throat centroid values by averaging adjacent pore coordinates 6 | 7 | """ 8 | 9 | from ._funcs import * 10 | -------------------------------------------------------------------------------- /openpnm/models/geometry/throat_centroid/_funcs.py: -------------------------------------------------------------------------------- 1 | import numpy as _np 2 | from openpnm.models.geometry import _geodocs 3 | 4 | 5 | __all__ = ["pore_coords"] 6 | 7 | 8 | @_geodocs 9 | def pore_coords( 10 | network 11 | ): 12 | r""" 13 | Calculate throat centroid values by averaging adjacent pore coordinates 14 | 15 | Parameters 16 | ---------- 17 | %(network)s 18 | 19 | Returns 20 | ------- 21 | values : ndarray 22 | A numpy ndarray containing throat centroid values 23 | 24 | """ 25 | conns = network['throat.conns'] 26 | coords = network['pore.coords'] 27 | return _np.mean(coords[conns], axis=1) 28 | -------------------------------------------------------------------------------- /openpnm/models/geometry/throat_cross_sectional_area/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Throat Cross-sectional Area Models 3 | ---------------------------------- 4 | 5 | This model contains a selection of functions for calculating 6 | throat cross-sectional area. 7 | 8 | """ 9 | 10 | from ._funcs import * 11 | -------------------------------------------------------------------------------- /openpnm/models/geometry/throat_cross_sectional_area/_funcs.py: -------------------------------------------------------------------------------- 1 | from numpy import pi as _pi 2 | from openpnm.models.geometry import _geodocs 3 | 4 | 5 | __all__ = ["cylinder", "cuboid", "rectangle"] 6 | 7 | 8 | @_geodocs 9 | def cylinder( 10 | network, 11 | throat_diameter='throat.diameter', 12 | ): 13 | r""" 14 | Calculate throat cross-sectional area for a cylindrical throat 15 | 16 | Parameters 17 | ---------- 18 | %(network)s 19 | %(Dt)s 20 | 21 | Returns 22 | ------- 23 | 24 | """ 25 | diams = network[throat_diameter] 26 | value = _pi / 4 * diams**2 27 | return value 28 | 29 | 30 | @_geodocs 31 | def cuboid( 32 | network, 33 | throat_diameter='throat.diameter', 34 | ): 35 | r""" 36 | Calculate throat cross-sectional area for a cuboid throat 37 | 38 | Parameters 39 | ---------- 40 | %(network)s 41 | %(Dt)s 42 | 43 | Returns 44 | ------- 45 | 46 | """ 47 | diams = network[throat_diameter] 48 | value = (diams)**2 49 | return value 50 | 51 | 52 | @_geodocs 53 | def rectangle( 54 | network, 55 | throat_diameter='throat.diameter', 56 | ): 57 | r""" 58 | Calculate throat cross-sectional area for a rectangular throat 59 | 60 | Parameters 61 | ---------- 62 | %(network)s 63 | %(Dt)s 64 | 65 | Returns 66 | ------- 67 | 68 | """ 69 | return network[throat_diameter] 70 | -------------------------------------------------------------------------------- /openpnm/models/geometry/throat_endpoints/__init__.py: -------------------------------------------------------------------------------- 1 | from ._funcs import * 2 | -------------------------------------------------------------------------------- /openpnm/models/geometry/throat_length/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Throat Length Models 3 | -------------------- 4 | 5 | This model contains a selection of functions for finding throat length 6 | assuming pores and throats have various shapes. 7 | 8 | """ 9 | 10 | from ._funcs import * 11 | -------------------------------------------------------------------------------- /openpnm/models/geometry/throat_perimeter/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Throat Perimeter Models 3 | ----------------------- 4 | 5 | This model contains a selection of functions for calcuating the 6 | throat perimeter assuming a circular/square/rectangular cross-section 7 | 8 | """ 9 | 10 | from ._funcs import * 11 | -------------------------------------------------------------------------------- /openpnm/models/geometry/throat_perimeter/_funcs.py: -------------------------------------------------------------------------------- 1 | import numpy as _np 2 | from openpnm.models.geometry import _geodocs 3 | 4 | 5 | __all__ = ["cylinder", 6 | "cuboid", 7 | "rectangle"] 8 | 9 | 10 | @_geodocs 11 | def cylinder( 12 | network, 13 | throat_diameter='throat.diameter', 14 | ): 15 | r""" 16 | Calcuate the throat perimeter assuming a circular cross-section 17 | 18 | Parameters 19 | ---------- 20 | %(network)s 21 | %(Dt)s 22 | 23 | Returns 24 | ------- 25 | perimeters : ndarray 26 | A numpy ndarray containing throat perimeter values 27 | 28 | """ 29 | return network[throat_diameter]*_np.pi 30 | 31 | 32 | @_geodocs 33 | def cuboid( 34 | network, 35 | throat_diameter='throat.diameter', 36 | ): 37 | r""" 38 | Calcuate the throat perimeter assuming a square cross-section 39 | 40 | Parameters 41 | ---------- 42 | %(network)s 43 | %(Dt)s 44 | 45 | Returns 46 | ------- 47 | 48 | """ 49 | return network[throat_diameter]*4 50 | 51 | 52 | @_geodocs 53 | def rectangle( 54 | network, 55 | throat_diameter='throat.diameter', 56 | ): 57 | r""" 58 | Calcuate the throat perimeter assuming a rectangular cross-section (2D) 59 | 60 | Parameters 61 | ---------- 62 | %(network)s 63 | %(Dt)s 64 | 65 | Returns 66 | ------- 67 | 68 | """ 69 | return 1.0 70 | -------------------------------------------------------------------------------- /openpnm/models/geometry/throat_seed/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Throat Seed Models 3 | ------------------ 4 | 5 | This model contains a selection of functions for calcuating the 6 | throat seed 7 | 8 | """ 9 | 10 | from ._funcs import * 11 | -------------------------------------------------------------------------------- /openpnm/models/geometry/throat_seed/_funcs.py: -------------------------------------------------------------------------------- 1 | from openpnm.models import misc as _misc 2 | 3 | 4 | __all__ = ["random", 5 | "from_neighbor_pores"] 6 | 7 | 8 | def random(network, seed=None, num_range=[0, 1]): 9 | return _misc.random(network, element='throat', seed=seed, 10 | num_range=num_range) 11 | 12 | 13 | random.__doc__ = _misc.random.__doc__ 14 | 15 | 16 | def from_neighbor_pores(network, prop='pore.seed', mode='min'): 17 | return _misc.from_neighbor_pores(network, prop=prop, 18 | mode=mode) 19 | 20 | 21 | random.__doc__ = _misc.from_neighbor_pores.__doc__ 22 | -------------------------------------------------------------------------------- /openpnm/models/geometry/throat_size/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Throat Size Models 3 | ------------------ 4 | 5 | This model contains a selection of functions for calcuating the 6 | throat size 7 | 8 | """ 9 | 10 | from ._funcs import * 11 | -------------------------------------------------------------------------------- /openpnm/models/geometry/throat_surface_area/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Throat Surface Area Models 3 | -------------------------- 4 | 5 | This model contains a selection of functions for calcuating the 6 | throat surface area 7 | 8 | """ 9 | 10 | from ._funcs import * 11 | -------------------------------------------------------------------------------- /openpnm/models/geometry/throat_surface_area/_funcs.py: -------------------------------------------------------------------------------- 1 | import numpy as _np 2 | from openpnm.models.geometry import _geodocs 3 | 4 | 5 | __all__ = ["cylinder", 6 | "cuboid", 7 | "extrusion", 8 | "rectangle"] 9 | 10 | 11 | @_geodocs 12 | def cylinder( 13 | network, 14 | throat_diameter='throat.diameter', 15 | throat_length='throat.length', 16 | ): 17 | r""" 18 | Calculate surface area for a cylindrical throat 19 | 20 | Parameters 21 | ---------- 22 | %(network)s 23 | %(Dt)s 24 | %(Lt)s 25 | 26 | Returns 27 | ------- 28 | surface_areas : ndarray 29 | A numpy ndarray containing throat surface area values 30 | 31 | """ 32 | return _np.pi * network[throat_diameter] * network[throat_length] 33 | 34 | 35 | @_geodocs 36 | def cuboid( 37 | network, 38 | throat_diameter='throat.diameter', 39 | throat_length='throat.length', 40 | ): 41 | r""" 42 | Calculate surface area for a cuboid throat 43 | 44 | Parameters 45 | ---------- 46 | %(network)s 47 | %(Dt)s 48 | %(Lt)s 49 | 50 | Returns 51 | ------- 52 | 53 | """ 54 | return 4 * network[throat_diameter] * network[throat_length] 55 | 56 | 57 | @_geodocs 58 | def extrusion( 59 | network, 60 | throat_perimeter='throat.perimeter', 61 | throat_length='throat.length', 62 | ): 63 | r""" 64 | Calculate surface area for an arbitrary shaped throat give the perimeter 65 | and length. 66 | 67 | Parameters 68 | ---------- 69 | %(network)s 70 | %(Pt)s 71 | %(Lt)s 72 | 73 | Returns 74 | ------- 75 | 76 | """ 77 | return network[throat_perimeter] * network[throat_length] 78 | 79 | 80 | @_geodocs 81 | def rectangle( 82 | network, 83 | throat_length='throat.length', 84 | ): 85 | r""" 86 | Calculate surface area for a rectangular throat 87 | 88 | Only suitable for true 2D simulations 89 | 90 | Parameters 91 | ---------- 92 | %(network)s 93 | %(Lt)s 94 | 95 | Returns 96 | ------- 97 | 98 | """ 99 | return 2 * network[throat_length] 100 | -------------------------------------------------------------------------------- /openpnm/models/geometry/throat_vector/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Throat Vector Model 3 | ------------------- 4 | 5 | Calculates throat vector as straight path between connected pores 6 | 7 | """ 8 | 9 | from ._funcs import * 10 | -------------------------------------------------------------------------------- /openpnm/models/geometry/throat_vector/_funcs.py: -------------------------------------------------------------------------------- 1 | from transforms3d import _gohlketransforms as tr 2 | from openpnm.models.geometry import _geodocs 3 | 4 | 5 | __all__ = ["pore_to_pore"] 6 | 7 | 8 | @_geodocs 9 | def pore_to_pore(network): 10 | r""" 11 | Calculates throat vector as straight path between connected pores. 12 | 13 | Parameters 14 | ---------- 15 | %(network)s 16 | 17 | Returns 18 | ------- 19 | unit_vec : ndarray 20 | A [Nt-by-3] numpy ndarray containing pore-to-pore unit vectors 21 | 22 | Notes 23 | ----- 24 | There is an important impicit assumption here: the positive direction is 25 | taken as the direction from the pore with the lower index to the higher. 26 | This corresponds to the pores in the 1st and 2nd columns of the 27 | 'throat.conns' array as stored on the network. 28 | 29 | """ 30 | conns = network['throat.conns'] 31 | P1 = conns[:, 0] 32 | P2 = conns[:, 1] 33 | coords = network['pore.coords'] 34 | vec = coords[P2] - coords[P1] 35 | unit_vec = tr.unit_vector(vec, axis=1) 36 | return unit_vec 37 | -------------------------------------------------------------------------------- /openpnm/models/geometry/throat_volume/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Throat Volume Models 3 | -------------------- 4 | 5 | This model contains a selection of functions for calcuating 6 | throat volume assuing various shapes 7 | 8 | """ 9 | 10 | from ._funcs import * 11 | -------------------------------------------------------------------------------- /openpnm/models/misc/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Miscellaneous 3 | ============= 4 | 5 | This submodule contains models for calculating general or generic values 6 | that are useful for all other pore-scale models, such as scaling values, or 7 | generating an array of random numbers. 8 | 9 | """ 10 | 11 | # The following bits are to initialize some boilerplate docstrings for docrep 12 | from openpnm.utils import Docorator as _doc 13 | _docstr = _doc() 14 | _docstr.params['models.misc.seeds'] = \ 15 | r"""seeds : str, optional 16 | Name of the dictionary key on ``target`` where the array containing 17 | random seed values (between 0 and 1) is stored. These values are 18 | used to do a reverse lookup on the cdf of given distribution using 19 | the ``ppf`` method on the scipy.stats object. Truncating the 20 | range (e.g. from 0.1 to 0.9) is often useful to prevent the 21 | occurance of extreme values from the long tails.""" 22 | 23 | 24 | from ._statistical_distributions import * 25 | from ._simple_equations import * 26 | from ._basic_math import * 27 | from ._neighbor_lookups import * 28 | -------------------------------------------------------------------------------- /openpnm/models/network/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Network 3 | ======= 4 | 5 | This submodule contains models for calculating topological properties of 6 | networks 7 | 8 | """ 9 | 10 | from ._topology import * 11 | from ._health import * 12 | -------------------------------------------------------------------------------- /openpnm/models/phase/critical_props/__init__.py: -------------------------------------------------------------------------------- 1 | from ._funcs import * 2 | -------------------------------------------------------------------------------- /openpnm/models/phase/density/__init__.py: -------------------------------------------------------------------------------- 1 | from ._funcs import * 2 | -------------------------------------------------------------------------------- /openpnm/models/phase/diffusivity/__init__.py: -------------------------------------------------------------------------------- 1 | from ._funcs import * 2 | -------------------------------------------------------------------------------- /openpnm/models/phase/heat_capacity/__init__.py: -------------------------------------------------------------------------------- 1 | from ._funcs import * 2 | -------------------------------------------------------------------------------- /openpnm/models/phase/misc/__init__.py: -------------------------------------------------------------------------------- 1 | from ._funcs import * 2 | -------------------------------------------------------------------------------- /openpnm/models/phase/misc/_funcs.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from openpnm.models.phase import _phasedocs 3 | 4 | 5 | __all__ = [ 6 | "mix_and_match", 7 | "mole_to_mass_fraction", 8 | ] 9 | 10 | 11 | @_phasedocs 12 | def mix_and_match( 13 | phase, 14 | prop, 15 | phases, 16 | occupancy, 17 | ): 18 | r""" 19 | Return the given property by looking it up from a list of given phases 20 | based on occupancy. 21 | 22 | Parameters 23 | ---------- 24 | %(phase)s 25 | prop : str 26 | The dictionary key to the array containing the pore/throat property to 27 | be used in the calculation. 28 | phases : list 29 | List of Phases over which the given `prop` is to be 30 | averaged out. 31 | occupancy : str 32 | The dictionary key to the array containing the occupancy associated 33 | with each of the given ``phases``. 34 | 35 | Returns 36 | ------- 37 | weighted_average : ndarray 38 | Weighted average of the given `prop` averaged over `phases`. 39 | 40 | """ 41 | # Hack for ModelsMixin to not complain (cyclic dep) 42 | prop = prop.strip("_") 43 | values = np.zeros_like(phases[0][prop]) 44 | 45 | for phase in phases: 46 | mask = phase[occupancy] 47 | values[mask] = phase[prop][mask] 48 | return values 49 | 50 | 51 | @_phasedocs 52 | def mole_to_mass_fraction( 53 | phase, 54 | MWs='param.molecular_weight.*', 55 | ): 56 | r""" 57 | Convert mole fraction to mass fraction 58 | 59 | Parameters 60 | ---------- 61 | %(phase)s 62 | %(MWs)s 63 | 64 | Returns 65 | ------- 66 | 67 | """ 68 | MWs = phase.get_comp_vals(MWs) 69 | xs = phase['pore.mole_fraction'] 70 | ms = {} 71 | # Find the actual masses in each pore 72 | for c in xs.keys(): 73 | ms[c] = xs[c]*MWs[c] 74 | # Normalize component mass by total mass in each pore 75 | denom = np.vstack(list(ms.values())).sum(axis=0) 76 | for c in xs.keys(): 77 | ms[c] = ms[c]/denom 78 | return ms 79 | -------------------------------------------------------------------------------- /openpnm/models/phase/mixtures/__init__.py: -------------------------------------------------------------------------------- 1 | from ._funcs import * 2 | -------------------------------------------------------------------------------- /openpnm/models/phase/partition_coefficient/__init__.py: -------------------------------------------------------------------------------- 1 | from ._funcs import * 2 | -------------------------------------------------------------------------------- /openpnm/models/phase/partition_coefficient/_funcs.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import numpy as np 4 | from pathlib import Path 5 | from openpnm.models.phase import _phasedocs 6 | 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | __all__ = ["gaseous_species_in_water"] 12 | 13 | 14 | @_phasedocs 15 | def gaseous_species_in_water( 16 | phase, 17 | T="throat.temperature", 18 | ): 19 | r""" 20 | Calculate Henry's law constant for gaseous species dissolved in water 21 | 22 | Parameters 23 | ---------- 24 | %(phase)s 25 | %(T)s 26 | 27 | Returns 28 | ------- 29 | H : ndarray 30 | A numpy ndarray containing Henry's law constant (Kpx) [atm/mol-frac] 31 | 32 | Notes 33 | ----- 34 | The constant for the correlation a lookup using the chemical formula 35 | stored in ``phase.params['formula']``. 36 | 37 | References 38 | ---------- 39 | Yaws, Carl L., et al. "Solubility & Henry's Law constants for sulfur 40 | compounds in water: unlike traditional methods, the new correlation and 41 | data presented here are appropriate for very low concentrations." 42 | Chemical Engineering 110.8 (2003): 60-65. 43 | 44 | """ 45 | import pandas as pd 46 | fname = "gas_water_henry.csv" 47 | path = Path(os.path.realpath(__file__), "../") 48 | path = Path(path.resolve(), fname) 49 | df = pd.read_csv(path) 50 | row = df[df.Formula == phase.params['formula']] 51 | A, B, C, D = row.iloc[0, 3:7].astype(float) 52 | Tmin, Tmax = row.iloc[0, 7:9].astype(float) 53 | T = phase[T] 54 | if (T.min() < Tmin) or (T.max() > Tmax): 55 | logger.critical("The correlation is only accurate for temperatures in the " 56 | + f"range of {Tmin:.1f}K and {Tmax:.1f}K!") 57 | Hpx_log10 = A + B/T + C*np.log10(T) + D*T 58 | return 10**Hpx_log10 59 | -------------------------------------------------------------------------------- /openpnm/models/phase/surface_tension/__init__.py: -------------------------------------------------------------------------------- 1 | from ._funcs import * 2 | -------------------------------------------------------------------------------- /openpnm/models/phase/thermal_conductivity/__init__.py: -------------------------------------------------------------------------------- 1 | from ._funcs import * 2 | -------------------------------------------------------------------------------- /openpnm/models/phase/vapor_pressure/__init__.py: -------------------------------------------------------------------------------- 1 | from ._funcs import * 2 | -------------------------------------------------------------------------------- /openpnm/models/phase/viscosity/__init__.py: -------------------------------------------------------------------------------- 1 | from ._funcs import * 2 | -------------------------------------------------------------------------------- /openpnm/models/physics/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Physics 3 | ------- 4 | 5 | This submodule contains models for calculating properties related to physical 6 | transport processes, including conductances, reaction rates, and capillary 7 | effects 8 | 9 | """ 10 | 11 | from . import ad_dif_conductance 12 | from . import diffusive_conductance 13 | from . import electrical_conductance 14 | from . import hydraulic_conductance 15 | from . import thermal_conductance 16 | from . import source_terms 17 | from . import capillary_pressure 18 | from . import meniscus 19 | from . import multiphase 20 | -------------------------------------------------------------------------------- /openpnm/models/physics/ad_dif_conductance/__init__.py: -------------------------------------------------------------------------------- 1 | from ._funcs import * 2 | -------------------------------------------------------------------------------- /openpnm/models/physics/capillary_pressure/__init__.py: -------------------------------------------------------------------------------- 1 | from ._funcs import * 2 | -------------------------------------------------------------------------------- /openpnm/models/physics/diffusive_conductance/__init__.py: -------------------------------------------------------------------------------- 1 | from ._funcs import * 2 | -------------------------------------------------------------------------------- /openpnm/models/physics/electrical_conductance/__init__.py: -------------------------------------------------------------------------------- 1 | from ._funcs import * 2 | -------------------------------------------------------------------------------- /openpnm/models/physics/hydraulic_conductance/__init__.py: -------------------------------------------------------------------------------- 1 | from ._funcs import * 2 | -------------------------------------------------------------------------------- /openpnm/models/physics/meniscus/__init__.py: -------------------------------------------------------------------------------- 1 | from ._funcs import * 2 | -------------------------------------------------------------------------------- /openpnm/models/physics/multiphase/__init__.py: -------------------------------------------------------------------------------- 1 | from ._funcs import * 2 | -------------------------------------------------------------------------------- /openpnm/models/physics/source_terms/__init__.py: -------------------------------------------------------------------------------- 1 | from ._funcs import * 2 | -------------------------------------------------------------------------------- /openpnm/models/physics/thermal_conductance/__init__.py: -------------------------------------------------------------------------------- 1 | from ._funcs import * 2 | -------------------------------------------------------------------------------- /openpnm/models/physics/thermal_conductance/_funcs.py: -------------------------------------------------------------------------------- 1 | from openpnm.models.physics._utils import _poisson_conductance 2 | from openpnm.models import _doctxt 3 | 4 | 5 | __all__ = ["generic_thermal", "series_resistors"] 6 | 7 | 8 | @_doctxt 9 | def generic_thermal(phase, 10 | pore_conductivity='pore.thermal_conductivity', 11 | throat_conductivity='throat.thermal_conductivity', 12 | size_factors='throat.diffusive_size_factors'): 13 | r""" 14 | Calculate the thermal conductance of conduits in network. 15 | 16 | Parameters 17 | ---------- 18 | %(phase)s 19 | pore_conductivity : str 20 | %(dict_blurb)s thermal conductivity 21 | throat_conductivity : str 22 | %(dict_blurb)s throat thermal conductivity 23 | size_factors : str 24 | %(dict_blurb)s conduit diffusive size factors 25 | 26 | Returns 27 | ------- 28 | %(return_arr)s thermal conductance 29 | 30 | """ 31 | return _poisson_conductance(phase=phase, 32 | pore_conductivity=pore_conductivity, 33 | throat_conductivity=throat_conductivity, 34 | size_factors=size_factors) 35 | 36 | 37 | @_doctxt 38 | def series_resistors( 39 | phase, 40 | pore_thermal_conductivity='pore.thermal_conductivity', 41 | throat_thermal_conductivity='throat.thermal_conductivity', 42 | size_factors='throat.diffusive_size_factors' 43 | ): 44 | r""" 45 | Calculate the thermal conductance of conduits in network. 46 | 47 | Parameters 48 | ---------- 49 | %(phase)s 50 | pore_conductivity : str 51 | %(dict_blurb)s thermal conductivity 52 | throat_conductivity : str 53 | %(dict_blurb)s throat thermal conductivity 54 | size_factors : str 55 | %(dict_blurb)s conduit diffusive size factors 56 | 57 | Returns 58 | ------- 59 | %(return_arr)s thermal conductance 60 | 61 | """ 62 | return _poisson_conductance(phase=phase, 63 | pore_conductivity=pore_thermal_conductivity, 64 | throat_conductivity=throat_thermal_conductivity, 65 | size_factors=size_factors) 66 | -------------------------------------------------------------------------------- /openpnm/network/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Network 3 | ======= 4 | 5 | Contains network generators and the basic Network class 6 | 7 | """ 8 | 9 | from ._network import Network 10 | from ._cubic import Cubic 11 | from ._demo import Demo 12 | from ._bcc import BodyCenteredCubic 13 | from ._fcc import FaceCenteredCubic 14 | from ._cubic_template import CubicTemplate 15 | from ._delaunay_voronoi_dual import DelaunayVoronoiDual 16 | from ._voronoi import Voronoi 17 | from ._delaunay import Delaunay 18 | -------------------------------------------------------------------------------- /openpnm/network/_cubic_template.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import numpy as np 3 | from openpnm.network import Network 4 | from openpnm._skgraph.generators import cubic_template 5 | from openpnm._skgraph.queries import find_coordination 6 | from openpnm._skgraph.tools import dimensionality, find_surface_nodes 7 | 8 | 9 | __all__ = ['CubicTemplate'] 10 | 11 | 12 | class CubicTemplate(Network): 13 | r""" 14 | Simple cubic lattice with arbitrary domain shape specified by a 15 | template image 16 | 17 | The class creates a standard Cubic network the same shape as the 18 | provided image, then trims pores from the network that are not in the 19 | mask. 20 | 21 | Parameters 22 | ---------- 23 | template : array_like 24 | The array (image) describing the desired shape of the domain. All 25 | locations in the image that are marked as ``True`` are kept while 26 | the rest of trimmed to yeild the shape. 27 | spacing : array_like, optional 28 | The spacing between pore centers in each direction. If not given, 29 | then [1, 1, 1] is assumed. 30 | name : str 31 | An optional name for the object to help identify it. If not given, 32 | one will be generated. 33 | 34 | Notes 35 | ----- 36 | The other arguments are the same as ``Cubic`` except that ``shape`` is 37 | inferred from the ``template`` image. 38 | 39 | """ 40 | 41 | def __init__(self, template, spacing=[1, 1, 1], label_surface_pores=False, **kwargs): 42 | super().__init__(**kwargs) 43 | template = np.atleast_3d(template) 44 | net = cubic_template(template=template, 45 | spacing=spacing, 46 | node_prefix='pore', 47 | edge_prefix='throat') 48 | self.update(net) 49 | if not label_surface_pores: 50 | return 51 | self['pore.surface'] = find_surface_nodes(self) 52 | ndims = dimensionality(self).sum() 53 | max_neighbors = 6 if ndims == 3 else 4 54 | num_neighbors = find_coordination(self, nodes=self.Ps) 55 | mask_internal_surface = (num_neighbors < max_neighbors) & ~self["pore.surface"] 56 | self.set_label("pore.internal_surface", pores=mask_internal_surface) 57 | -------------------------------------------------------------------------------- /openpnm/network/_demo.py: -------------------------------------------------------------------------------- 1 | from openpnm.network import Cubic 2 | from openpnm.utils import Docorator 3 | from openpnm.models.collections.geometry import spheres_and_cylinders 4 | 5 | 6 | docstr = Docorator() 7 | 8 | 9 | __all__ = ['Cubic'] 10 | 11 | 12 | @docstr.dedent 13 | class Demo(Cubic): 14 | r""" 15 | A shortcut for generating a cubic network with geometrical properties 16 | already added. 17 | 18 | Parameters 19 | ---------- 20 | %(Network.parameters)s 21 | 22 | """ 23 | 24 | def __init__(self, shape=[3, 3, 1], **kwargs): 25 | super().__init__(shape=shape, **kwargs) 26 | self.add_model_collection(spheres_and_cylinders) 27 | self.regenerate_models() 28 | -------------------------------------------------------------------------------- /openpnm/phase/_air.py: -------------------------------------------------------------------------------- 1 | from openpnm.models.collections.phase import air 2 | from openpnm.phase import Phase, _fetch_chemical_props 3 | from openpnm.phase import StandardGas, StandardGasMixture 4 | from openpnm.utils import Docorator 5 | 6 | 7 | docstr = Docorator() 8 | 9 | 10 | __all__ = [ 11 | 'Air', 12 | '_AirMixture', 13 | ] 14 | 15 | 16 | @docstr.dedent 17 | class Air(Phase): 18 | r""" 19 | Creates a Phase object with preset models and values for air 20 | 21 | Parameters 22 | ---------- 23 | %(Phase.parameters)s 24 | 25 | """ 26 | 27 | def __init__(self, **kwargs): 28 | super().__init__(**kwargs) 29 | from thermo import Mixture 30 | a = Mixture(IDs=['o2', 'n2'], zs=[0.21, 0.79]) 31 | temp = _fetch_chemical_props(a) 32 | self.params.update(temp) 33 | self.models.update(air) 34 | self.regenerate_models() 35 | 36 | 37 | class _AirMixture(StandardGasMixture): # pragma: no cover 38 | 39 | def __init__(self, network, **kwargs): 40 | o2 = StandardGas(network=network, species='o2') 41 | n2 = StandardGas(network=network, species='n2') 42 | super().__init__(network=network, components=[o2, n2], **kwargs) 43 | self.y(o2, 0.21) 44 | self.y(n2, 0.79) 45 | self.regenerate_models() 46 | -------------------------------------------------------------------------------- /openpnm/phase/_mercury.py: -------------------------------------------------------------------------------- 1 | from openpnm.models.collections.phase import mercury 2 | from openpnm.phase import Phase, _fetch_chemical_props 3 | 4 | 5 | __all__ = [ 6 | 'Mercury', 7 | ] 8 | 9 | 10 | class Mercury(Phase): 11 | r""" 12 | Creates Phase object with and preset values and pore-scale models for 13 | mercury 14 | 15 | Parameters 16 | ---------- 17 | %(Phase.parameters)s 18 | 19 | """ 20 | 21 | def __init__(self, **kwargs): 22 | super().__init__(**kwargs) 23 | from thermo import Chemical 24 | a = Chemical('hg') 25 | temp = _fetch_chemical_props(a) 26 | self.params.update(temp) 27 | self.add_model_collection(mercury) 28 | self.regenerate_models() 29 | -------------------------------------------------------------------------------- /openpnm/phase/_water.py: -------------------------------------------------------------------------------- 1 | from openpnm.models.collections.phase import water 2 | from openpnm.phase import Phase, _fetch_chemical_props 3 | from openpnm.utils import Docorator 4 | 5 | 6 | docstr = Docorator() 7 | 8 | 9 | __all__ = [ 10 | 'Water', 11 | ] 12 | 13 | 14 | @docstr.dedent 15 | class Water(Phase): 16 | r""" 17 | Creates Phase object with preset values for Water 18 | 19 | Parameters 20 | ---------- 21 | %(Phase.parameters)s 22 | 23 | """ 24 | 25 | def __init__(self, **kwargs): 26 | super().__init__(**kwargs) 27 | from thermo import Chemical 28 | a = Chemical('h2o') 29 | temp = _fetch_chemical_props(a) 30 | self.params.update(temp) 31 | self.models.update(water) 32 | self.regenerate_models() 33 | -------------------------------------------------------------------------------- /openpnm/solvers/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Collection of matrix solvers for steady state simulations 3 | ========================================================= 4 | 5 | The ``solvers`` module contains wrapper classes for sparse matrix solvers. 6 | 7 | """ 8 | 9 | from ._base import * 10 | from ._scipy import * 11 | from ._pardiso import * 12 | from ._petsc import * 13 | from ._pyamg import * 14 | -------------------------------------------------------------------------------- /openpnm/solvers/_base.py: -------------------------------------------------------------------------------- 1 | from numpy.linalg import norm 2 | 3 | __all__ = ['BaseSolver', 'DirectSolver', 'IterativeSolver'] 4 | 5 | 6 | class BaseSolver: 7 | """Base class for all solvers.""" 8 | def __init__(self): 9 | ... 10 | 11 | def solve(self): 12 | """Solves the given linear system of equations Ax=b.""" 13 | raise NotImplementedError 14 | 15 | 16 | class DirectSolver(BaseSolver): 17 | """Base class for all direct solvers.""" 18 | ... 19 | 20 | 21 | class IterativeSolver(BaseSolver): 22 | """Base class for iterative solvers.""" 23 | def __init__(self, tol=1e-8, maxiter=1000): 24 | self.tol = tol 25 | self.maxiter = maxiter 26 | self.atol = None # needs to be evaluated later 27 | self.rtol = None # needs to be evaluated later 28 | 29 | def _get_atol(self, b): 30 | r""" 31 | Returns the absolute tolerance ``atol`` that corresponds to the 32 | the given tolerance ``tol``. 33 | 34 | Notes 35 | ----- 36 | ``atol`` is calculated to satisfy the following stopping criterion: 37 | ``norm(A*x-b)`` <= ``atol`` 38 | 39 | """ 40 | return norm(b) * self.tol 41 | 42 | def _get_rtol(self, A, b, x0): 43 | r""" 44 | Returns the relative tolerance ``rtol`` that corresponds to the 45 | the given tolerance ``tol``. 46 | 47 | Notes 48 | ----- 49 | ``rtol`` is defined based on the following formula: 50 | ``rtol = residual(@x_final) / residual(@x0)`` 51 | 52 | """ 53 | res0 = self._get_residual(A, b, x0) 54 | atol = self._get_atol(b) 55 | rtol = atol / res0 56 | return rtol 57 | 58 | def _get_residual(self, A, b, x): 59 | r""" 60 | Calculates the residual based on the given ``x`` using: 61 | ``res = norm(A*x - b)`` 62 | """ 63 | return norm(A * x - b) 64 | -------------------------------------------------------------------------------- /openpnm/solvers/_pardiso.py: -------------------------------------------------------------------------------- 1 | from scipy.sparse import csc_matrix, csr_matrix 2 | 3 | from openpnm.solvers import DirectSolver 4 | 5 | __all__ = ['PardisoSpsolve'] 6 | 7 | 8 | class PardisoSpsolve(DirectSolver): 9 | """Solves a linear system using ``pypardiso.spsolve``.""" 10 | 11 | def solve(self, A, b, **kwargs): 12 | """Solves the given linear system of equations Ax=b.""" 13 | from pypardiso import spsolve 14 | 15 | if not isinstance(A, (csr_matrix, csc_matrix)): 16 | A = A.tocsr() 17 | return (spsolve(A, b), 0) 18 | -------------------------------------------------------------------------------- /openpnm/solvers/_pyamg.py: -------------------------------------------------------------------------------- 1 | import pyamg 2 | from scipy.sparse import csr_matrix 3 | 4 | from ._base import IterativeSolver 5 | 6 | __all__ = ['PyamgRugeStubenSolver'] 7 | 8 | 9 | # write a PyamgRugeStubenSolver class 10 | class PyamgRugeStubenSolver(IterativeSolver): 11 | """Iterative solver based on PyAMG's `ruge_stuben_solver`.""" 12 | 13 | def solve(self, A, b, x0=None): 14 | if not isinstance(A, csr_matrix): 15 | A = A.tocsr() 16 | ml = pyamg.ruge_stuben_solver(A) 17 | return ml.solve(b, x0=x0, tol=self.tol, maxiter=self.maxiter, return_info=True) 18 | -------------------------------------------------------------------------------- /openpnm/solvers/_scipy.py: -------------------------------------------------------------------------------- 1 | from scipy.sparse import csr_matrix, csc_matrix 2 | from scipy.sparse.linalg import spsolve, cg 3 | from openpnm.solvers import DirectSolver, IterativeSolver 4 | 5 | __all__ = ['ScipySpsolve', 'ScipyCG'] 6 | 7 | 8 | class ScipySpsolve(DirectSolver): 9 | """Solves a linear system using ``scipy.sparse.linalg.spsolve``.""" 10 | 11 | def solve(self, A, b, **kwargs): 12 | """Solves the given linear system of equations Ax=b.""" 13 | if not isinstance(A, (csr_matrix, csc_matrix)): 14 | A = A.tocsr() 15 | return (spsolve(A, b), 0) 16 | 17 | 18 | class ScipyCG(IterativeSolver): 19 | """Solves a linear system using ``scipy.sparse.linalg.cg``.""" 20 | 21 | def solve(self, A, b, **kwargs): 22 | """Solves the given linear system of equations Ax=b.""" 23 | if not isinstance(A, (csr_matrix, csc_matrix)): 24 | A = A.tocsr() 25 | atol = self._get_atol(b) 26 | try: 27 | return cg(A, b, tol=self.tol, atol=atol, **kwargs) 28 | except TypeError: 29 | return cg(A, b, rtol=self.tol, atol=atol, **kwargs) 30 | -------------------------------------------------------------------------------- /openpnm/topotools/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Collection of functions for manipulating network topology 3 | ========================================================= 4 | 5 | This module contains a selection of functions that deal specifically with 6 | network topology. 7 | 8 | """ 9 | 10 | from ._topotools import * 11 | from ._perctools import * 12 | from ._graphtools import * 13 | -------------------------------------------------------------------------------- /openpnm/utils/__init__.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Utilities and helper classes/functions 3 | ====================================== 4 | 5 | This module contains two very important classes (Project and Workspace) 6 | as well as a number of helper classes. 7 | 8 | """ 9 | 10 | from re import L 11 | from ._misc import * 12 | from ._settings import * 13 | from ._workspace import * 14 | from ._project import * 15 | from ._health import * 16 | 17 | 18 | def _get_version(): 19 | from openpnm.__version__ import __version__ as ver 20 | 21 | suffix = ".dev0" 22 | if ver.endswith(suffix): 23 | ver = ver[: -len(suffix)] 24 | return ver 25 | 26 | 27 | def _setup_logger_rich(): 28 | import logging 29 | from rich.logging import RichHandler 30 | 31 | FORMAT = "%(message)s" 32 | logging.basicConfig( 33 | format=FORMAT, datefmt="[%X]", handlers=[RichHandler(rich_tracebacks=True)] 34 | ) 35 | 36 | 37 | _setup_logger_rich() 38 | -------------------------------------------------------------------------------- /openpnm/utils/jgf_schema.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PMEAL/OpenPNM/7fd91d9d5accafd8ac58f343b4ce16bc143640fe/openpnm/utils/jgf_schema.pkl -------------------------------------------------------------------------------- /openpnm/visualization/__init__.py: -------------------------------------------------------------------------------- 1 | from ._plottools import * 2 | from ._conduit_visualizer import * 3 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | minversion = 6.0 3 | python_files = *.py 4 | python_classes = *Test 5 | python_functions = test_* 6 | console_output_style = classic 7 | ;testpaths = 8 | ; tests 9 | ; examples 10 | addopts = 11 | --doctest-modules 12 | --doctest-glob='*.rst' 13 | --ignore=docs/conf.py 14 | --ignore=setup.py 15 | --ignore="scripts/" 16 | --ignore="tests/unit/contrib" 17 | --ignore="tests/unit/metrics" 18 | -p no:warnings 19 | norecursedirs = 20 | .git 21 | .github 22 | build 23 | dist 24 | locals 25 | ;filterwarnings = ignore::DeprecationWarning 26 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | --index-url https://pypi.python.org/simple/ 2 | 3 | -e . 4 | -------------------------------------------------------------------------------- /requirements/conda.txt: -------------------------------------------------------------------------------- 1 | chemicals 2 | docrep 3 | h5py 4 | jsonschema 5 | matplotlib 6 | networkx 7 | numba 8 | numpy 9 | pandas 10 | pyamg 11 | pypardiso 12 | scikit-image 13 | scipy 14 | sympy 15 | thermo 16 | tqdm 17 | transforms3d 18 | -------------------------------------------------------------------------------- /requirements/docs.txt: -------------------------------------------------------------------------------- 1 | ipympl 2 | jupyterlab_widgets 3 | myst-nb 4 | numpydoc 5 | pandoc 6 | plotly 7 | pydata-sphinx-theme 8 | sphinx 9 | sphinx-copybutton 10 | sphinx-design 11 | porespy 12 | -------------------------------------------------------------------------------- /requirements/tests.txt: -------------------------------------------------------------------------------- 1 | jupyter 2 | nbval 3 | netgen-mesher 4 | porespy 5 | py 6 | pytest 7 | pytest-cache 8 | pytest-cov 9 | thermo 10 | -------------------------------------------------------------------------------- /scripts/chemical_example.py: -------------------------------------------------------------------------------- 1 | import openpnm as op 2 | from openpnm.phase import mixtures 3 | ws = op.Workspace() 4 | proj = ws.new_project() 5 | 6 | pn = op.network.Cubic(shape=[10, 10, 10], spacing=1e-4, project=proj) 7 | geo = op.geometry.SpheresAndCylinders(network=pn, pores=pn.Ps, throats=pn.Ts) 8 | 9 | o2 = mixtures.GasByName(network=pn, species='oxygen', name='o2') 10 | n2 = mixtures.GasByName(network=pn, species='nitrogen', name='n2') 11 | air = mixtures.GasMixture(network=pn, components=[o2, n2]) 12 | air.set_mole_fraction(component=o2, values=0.21) 13 | air.update_mole_fractions() 14 | air.regenerate_models() 15 | 16 | water = mixtures.LiquidByName(network=pn, species='water', name='h2o') 17 | ethanol = mixtures.LiquidByName(network=pn, species='ethanol', name='etOH') 18 | vodka = mixtures.LiquidMixture(network=pn, components=[water, ethanol]) 19 | vodka.set_mole_fraction(component=ethanol, values=0.40) 20 | vodka.update_mole_fractions() 21 | vodka.regenerate_models() 22 | -------------------------------------------------------------------------------- /scripts/example_bundle_of_tubes.py: -------------------------------------------------------------------------------- 1 | import openpnm as op 2 | from openpnm.materials import BundleOfTubes 3 | 4 | 5 | ws = op.Workspace() 6 | ws.settings['loglevel'] = 30 7 | proj = ws.new_project() 8 | 9 | net, geo, phase = BundleOfTubes( 10 | shape=50, 11 | spacing=0.0001, 12 | length=0.001, 13 | psd_params={ 14 | 'distribution': 'weibull', 15 | 'loc': 1e-6, 16 | 'scale': 4e-5, 17 | 'shape': 2.2 18 | }, 19 | settings={'adjust_psd': 'normalize', 'seed': 0} 20 | ) 21 | 22 | hg = op.phase.Mercury(network=net) 23 | phys = op.physics.Classic(network=net, phase=hg, geometry=geo) 24 | 25 | mip = op.algorithms.Porosimetry(network=net, phase=hg) 26 | mip.set_inlets(pores=net.pores('top')) 27 | mip.run() 28 | mip.plot_intrusion_curve() 29 | -------------------------------------------------------------------------------- /scripts/example_knudsen_diffusion.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import numpy as np 3 | import openpnm as op 4 | 5 | 6 | # Get Deff w/o including Knudsen effect 7 | shape = np.array([10, 10, 10]) 8 | spacing = 1.0 9 | net = op.network.Cubic(shape=[10, 10, 10], spacing=spacing) 10 | geom = op.geometry.SpheresAndCylinders(network=net, pores=net.Ps, throats=net.Ts) 11 | air = op.phase.Air(network=net) 12 | phys = op.physics.Standard(network=net, geometry=geom, phase=air) 13 | odiff = op.models.physics.diffusive_conductance.ordinary_diffusion 14 | phys.add_model(propname="throat.diffusive_conductance", model=odiff) 15 | fd = op.algorithms.FickianDiffusion(network=net, phase=air) 16 | fd.set_value_BC(pores=net.pores("left"), values=1.0) 17 | fd.set_value_BC(pores=net.pores("right"), values=0.0) 18 | fd.run() 19 | L = (shape * spacing)[1] 20 | A = (shape * spacing)[[0, 2]].prod() 21 | Mdot = fd.rate(pores=net.pores("left")).squeeze() 22 | Deff0 = Mdot * L / A 23 | 24 | # Get Deff w/ including Knudsen effect 25 | mdiff = op.models.physics.diffusive_conductance.mixed_diffusion 26 | phys.add_model(propname="throat.diffusive_conductance", model=mdiff) 27 | spacings = np.linspace(1e-9, 1e-4, 20) 28 | spacings = np.logspace(-9, -3, 25) 29 | Deff = [] 30 | 31 | for spacing in spacings: 32 | np.random.seed(10) 33 | net = op.network.Cubic(shape=[10, 10, 10], spacing=spacing) 34 | geom = op.geometry.SpheresAndCylinders(network=net, pores=net.Ps, throats=net.Ts) 35 | air = op.phase.Air(network=net) 36 | phys = op.physics.Standard(network=net, geometry=geom, phase=air) 37 | phys.add_model(propname="throat.diffusive_conductance", model=mdiff) 38 | fd = op.algorithms.FickianDiffusion(network=net, phase=air) 39 | fd.set_value_BC(pores=net.pores("left"), values=1.0) 40 | fd.set_value_BC(pores=net.pores("right"), values=0.0) 41 | fd.run() 42 | L = (shape * spacing)[1] 43 | A = (shape * spacing)[[0, 2]].prod() 44 | Mdot = fd.rate(pores=net.pores("left")).squeeze() 45 | Deff.append(Mdot * L / A) 46 | 47 | # Plot ratio of Deff w/ Knudsen to that w/o 48 | Deff = np.array(Deff) 49 | plt.figure() 50 | plt.plot(spacings, Deff/Deff0) 51 | plt.xscale("log") 52 | plt.xlabel("spacing (m)") 53 | plt.ylabel("Deff/Deff0") 54 | -------------------------------------------------------------------------------- /scripts/example_mass_partitioning.py: -------------------------------------------------------------------------------- 1 | r""" 2 | Example: How to use OpenPNNM to simulating multiphase Fickian diffusion 3 | 4 | 1D network, the first half of the network is occupied by air and the next 5 | half by water. A partition coefficient of 0.5 is assumed, meaning that the 6 | concentration of the diffusing species in water "at the interface" is half 7 | of that in air. 8 | 9 | """ 10 | import openpnm as op 11 | import matplotlib.pyplot as plt 12 | import numpy as np 13 | np.random.seed(10) 14 | 15 | # Define network and geometry 16 | net = op.network.Cubic(shape=[10, 1, 1]) 17 | geom = op.geometry.SpheresAndCylinders(network=net, pores=net.Ps, throats=net.Ts) 18 | 19 | # Define constituent phases 20 | air = op.phase.Air(network=net, name="air") 21 | water = op.phase.Water(network=net, name="water") 22 | water["pore.diffusivity"] = air["pore.diffusivity"] * 0.2 23 | 24 | # Define MultiPhase object 25 | mphase = op.contrib.MultiPhase(network=net, phases=[air, water]) 26 | mphase._set_automatic_throat_occupancy() 27 | mphase.set_occupancy(phase=air, pores=[0, 1, 2, 3, 4]) 28 | mphase.set_occupancy(phase=water, pores=[5, 6, 7, 8, 9]) 29 | const = op.models.misc.constant 30 | mphase.set_binary_partition_coef(phases=[water, air], model=const, value=0.5) 31 | 32 | # Define physics object 33 | phys = op.physics.Standard(network=net, phase=mphase, geometry=geom) 34 | mdiff = op.models.physics.diffusive_conductance.multiphase_diffusion 35 | phys.add_model(propname="throat.diffusive_conductance", model=mdiff) 36 | 37 | # Define algorithm: Fickian diffusion 38 | fd = op.algorithms.FickianDiffusion(network=net, phase=mphase) 39 | fd.set_value_BC(pores=0, values=1.0) 40 | fd.set_value_BC(pores=9, values=0.1) 41 | fd.run() 42 | 43 | # Post-processing 44 | c = fd["pore.concentration"] 45 | plt.figure() 46 | plt.plot(c, "ko:") 47 | plt.xlabel("x (m)") 48 | plt.ylabel("c (mol/m3)") 49 | -------------------------------------------------------------------------------- /scripts/example_mixtures.py: -------------------------------------------------------------------------------- 1 | import openpnm as op 2 | from openpnm.phase import mixtures 3 | 4 | 5 | ws = op.Workspace() 6 | proj = ws.new_project() 7 | 8 | pn = op.network.Cubic(shape=[30, 30, 10], spacing=1e-4, project=proj) 9 | geo = op.geometry.SpheresAndCylinders(network=pn, pores=pn.Ps, throats=pn.Ts) 10 | 11 | N2 = mixtures.species.gases.N2(network=pn, name='pure_N2') 12 | O2 = mixtures.species.gases.O2(network=pn, name='pure_O2') 13 | air = mixtures.GenericMixture(network=pn, components=[N2, O2]) 14 | air.set_mole_fraction(N2, 0.79) 15 | air.set_mole_fraction(O2, 0.21) 16 | 17 | air.add_model(propname='pore.diffusivity.pure_O2', 18 | model=op.models.phase.mixtures.fuller_diffusivity) 19 | air.add_model(propname='pore.viscosity', 20 | model=op.models.misc.polynomial, 21 | prop='pore.temperature', 22 | a=[0.00000182082, 6.51815E-08, -3.48553E-11, 1.11409E-14]) 23 | 24 | phys = op.physics.GenericPhysics(network=pn, phase=air, geometry=geo) 25 | phys.add_model(propname='throat.diffusive_conductance', 26 | pore_diffusivity='pore.diffusivity.pure_O2', 27 | throat_diffusivity='throat.diffusivity.pure_O2', 28 | model=op.models.physics.diffusive_conductance.ordinary_diffusion) 29 | phys.add_model(propname='throat.hydraulic_conductance', 30 | pore_viscosity='pore.viscosity', 31 | model=op.models.physics.hydraulic_conductance.classic_hagen_poiseuille) 32 | 33 | sf = op.algorithms.StokesFlow(network=pn, phase=air) 34 | sf.set_value_BC(pores=pn.pores('left'), values=200000) 35 | sf.set_value_BC(pores=pn.pores('right'), values=100000) 36 | sf.run() 37 | air.update(sf.results()) 38 | 39 | air.regenerate_models() 40 | 41 | fd = op.algorithms.FickianDiffusion(network=pn, phase=air) 42 | fd.settings["quantity"] = 'pore.concentration.pure_O2' 43 | fd.set_value_BC(pores=pn.pores('left'), values=1) 44 | fd.set_value_BC(pores=pn.pores('right'), values=0) 45 | fd.run() 46 | air.update(fd.results()) 47 | -------------------------------------------------------------------------------- /scripts/example_non_newtonian.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import openpnm as op 3 | np.random.seed(7) 4 | 5 | 6 | # Workspace and project 7 | ws = op.Workspace() 8 | proj = ws.new_project() 9 | 10 | # Create network and geometry 11 | net = op.network.Cubic(shape=[20, 3, 3], spacing=1e-4, project=proj) 12 | geo = op.geometry.SpheresAndCylinders(network=net, pores=net.Ps, throats=net.Ts) 13 | 14 | # Create phase 15 | phase = op.phase.Water(network=net) 16 | phase['pore.consistency'] = 4.2e-2 # Pa.s^n 17 | phase['pore.flow_index'] = 0.52 18 | phase['pore.viscosity_min'] = 0.001 19 | phase['pore.viscosity_max'] = 100 20 | 21 | # Create physics 22 | phys = op.physics.GenericPhysics(network=net, phase=phase, geometry=geo) 23 | mod1 = op.models.physics.hydraulic_conductance.hagen_poiseuille 24 | phys.add_model(propname='throat.hydraulic_conductance', 25 | model=mod1, regen_mode='normal') 26 | 27 | # Algorithms: Newtonian Stokes flow 28 | sf = op.algorithms.StokesFlow(network=net, phase=phase) 29 | sf.set_value_BC(pores=net.pores('front'), values=1) 30 | sf.set_value_BC(pores=net.pores('back'), values=2) 31 | sf.run() 32 | phase.update(sf.results()) 33 | phase['pore.pressure_sf'] = phase['pore.pressure'] 34 | 35 | mod2 = op.models.physics.hydraulic_conductance.hagen_poiseuille_power_law 36 | phys.add_model(propname='throat.nonNewtonian_hydraulic_conductance', 37 | model=mod2, regen_mode='normal') 38 | 39 | # Algorithms: Non Newtonian Stokes flow 40 | nnsf = op.algorithms.NonNewtonianStokesFlow(network=net, phase=phase) 41 | nnsf.set_value_BC(pores=net.pores('front'), values=1) 42 | nnsf.set_value_BC(pores=net.pores('back'), values=2) 43 | nnsf.run() 44 | phase.update(nnsf.results()) 45 | 46 | cn = net['throat.conns'] 47 | gh_sf = phase['throat.hydraulic_conductance'] 48 | gh = phase['throat.nonNewtonian_hydraulic_conductance'] 49 | P_sf = phase['pore.pressure_sf'] 50 | P = phase['pore.pressure'] 51 | Qt_sf = np.abs(gh_sf*np.diff(P_sf[cn], axis=1).squeeze()) 52 | Qt = np.abs(gh*np.diff(P[cn], axis=1).squeeze()) 53 | Q = Qt/Qt_sf 54 | 55 | # Output results to a vtk file 56 | proj.export_data(phases=[phase], filename='out', filetype='XDMF') 57 | -------------------------------------------------------------------------------- /scripts/example_pnflow.py: -------------------------------------------------------------------------------- 1 | import openpnm as op 2 | ws = op.Workspace() 3 | ws.settings['loglevel'] = 30 4 | 5 | pn = op.network.Cubic(shape=[10, 10, 10], spacing=1e-4) 6 | geo = op.geometry.SpheresAndCylinders(network=pn, pores=pn.Ps, throats=pn.Ts) 7 | air = op.phase.Air(network=pn) 8 | phys = op.physics.Standard(network=pn, phase=air, geometry=geo) 9 | F = op.metrics.FormationFactor(network=pn) 10 | F.run() 11 | 12 | 13 | op.io.Statoil.add_reservoir_pore(network=pn, pores=pn.pores('left')) 14 | op.io.Statoil.add_reservoir_pore(network=pn, pores=pn.pores('right')) 15 | pn.add_model(propname='throat.total_length', 16 | model=op.models.geometry.throat_length.ctc) 17 | pn['throat.shape_factor'] = 1.0 18 | pn['pore.shape_factor'] = 1.0 19 | path = "./" 20 | prefix = pn.project.name 21 | op.io.Statoil.export_data(network=pn, path=path, prefix=prefix, 22 | shape=[1e-3, 1e-3, 1e-3]) 23 | # op.metrics.PNFlow.run(pn.project.name, path=path) 24 | -------------------------------------------------------------------------------- /scripts/example_salome_export.py: -------------------------------------------------------------------------------- 1 | r""" 2 | In the example script a generic network is created then exported as a 3 | Salome Python script. The script should be executed from Salome with 4 | "load script". The geometry is then built. The geometry generation on 5 | Salome may take some time depending on the number of pores. 6 | 7 | """ 8 | import numpy as np 9 | import openpnm as op 10 | 11 | 12 | # Workspace and project 13 | ws = op.Workspace() 14 | proj = ws.new_project() 15 | export = False 16 | 17 | # Network 18 | np.random.seed(7) 19 | net = op.network.Cubic(shape=[4, 3, 3], spacing=1e-4, project=proj) 20 | 21 | # Geometry 22 | geo = op.geometry.SpheresAndCylinders(network=net, pores=net.Ps, throats=net.Ts) 23 | 24 | # Phase 25 | phase = op.phase.Water(network=net) 26 | 27 | # Export the network 28 | if export: 29 | proj.export_data(phases=[phase], filename='out', filetype='Salome') 30 | -------------------------------------------------------------------------------- /scripts/example_statoil.py: -------------------------------------------------------------------------------- 1 | import openpnm as op 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | 5 | pn = op.network.Cubic(shape=[10, 10, 10], spacing=1e-4) 6 | geo = op.geometry.SpheresAndCylinders(network=pn, pores=pn.Ps, throats=pn.Ts) 7 | air = op.phase.Air(network=pn, name='air') 8 | water = op.phase.Water(network=pn, name='h2o') 9 | phys_air = op.physics.Standard(network=pn, phase=air, geometry=geo) 10 | phys_water = op.physics.Standard(network=pn, phase=water, geometry=geo) 11 | 12 | 13 | ip = op.algorithms.InvasionPercolation(network=pn, phase=water) 14 | ip.set_inlets(pores=pn.pores('left')) 15 | ip.run() 16 | 17 | 18 | Krel = [] 19 | for s in np.linspace(0, pn.Nt, 10): 20 | inv = ip['throat.invasion_sequence'] < s 21 | phys_air['throat.hydraulic_conductance'][inv] *= 1e-5 22 | perm_a = op.algorithms.StokesFlow(network=pn, phase=air) 23 | perm_a.set_value_BC(pores=pn.pores('top'), values=1) 24 | perm_a.set_value_BC(pores=pn.pores('bottom'), values=0) 25 | perm_a.run() 26 | Krel.append(perm_a.rate(pores=pn.pores('top'))) 27 | plt.plot(np.linspace(0, pn.Nt, 10)/pn.Nt, Krel) 28 | 29 | # Export to Statoil format. 30 | # Add reservoir pores on each end 31 | op.io.Statoil.add_reservoir_pore(network=pn, 32 | pores=pn.pores('left'), 33 | offset=0.25) 34 | op.io.Statoil.add_reservoir_pore(network=pn, 35 | pores=pn.pores('right'), 36 | offset=0.25) 37 | op.io.Statoil.export_data(network=pn, shape=[10, 10, 10]) 38 | -------------------------------------------------------------------------------- /scripts/example_subdomains.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import openpnm as op 3 | ws = op.Workspace() 4 | proj = ws.new_project() 5 | 6 | pn = op.network.Cubic(shape=[10, 10, 10], spacing=1e-4, project=proj) 7 | Ps = pn['pore.coords'][:, 0] < pn['pore.coords'][:, 0].mean() 8 | Ts = pn.find_neighbor_throats(pores=Ps, mode='xnor') 9 | geo1 = op.geometry.SpheresAndCylinders(network=pn, pores=Ps, throats=Ts) 10 | 11 | Ps = pn['pore.coords'][:, 0] >= pn['pore.coords'][:, 0].mean() 12 | Ts = pn.find_neighbor_throats(pores=Ps, mode='or') 13 | geo2 = op.geometry.SpheresAndCylinders(network=pn, pores=Ps, throats=Ts) 14 | 15 | pn['pore.foo'] = 1 16 | # Can't create a subdict below foo 17 | with pytest.raises(Exception): 18 | pn['pore.foo.bar'] = 1 19 | # Can create a subdict directly 20 | pn['pore.baz.bar'] = 2 21 | # Can't create a new item already used as subdict 22 | with pytest.raises(Exception): 23 | pn['pore.baz'] = 2 24 | 25 | # Also works on subdomains 26 | geo1['pore.blah'] = 1 27 | with pytest.raises(Exception): 28 | geo1['pore.blah.boo'] = 1 29 | geo1['pore.bee.bop'] = 1 30 | with pytest.raises(Exception): 31 | geo1['pore.bee'] = 1 32 | 33 | # Now start looking across objects 34 | with pytest.raises(Exception): 35 | geo1['pore.foo'] = 1 # Already exists on pn 36 | with pytest.raises(Exception): 37 | geo1['pore.foo.bar'] = 1 # pore.foo already exists on pn 38 | with pytest.raises(Exception): 39 | geo1['pore.baz'] = 1 # pore.baz.bar already exists on pn 40 | 41 | # Now start looking across objects 42 | geo2['pore.blah'] = 1 43 | geo2['pore.bee.bop'] = 1 44 | with pytest.raises(Exception): 45 | geo1['pore.bee'] = 1 46 | 47 | with pytest.raises(Exception): 48 | pn['pore.bee'] = 1 49 | 50 | with pytest.raises(Exception): 51 | pn['pore.bee.bop'] = 1 52 | -------------------------------------------------------------------------------- /scripts/example_transient_diffusion.py: -------------------------------------------------------------------------------- 1 | import openpnm as op 2 | import numpy as np 3 | 4 | 5 | # Workspace and project 6 | ws = op.Workspace() 7 | proj = ws.new_project() 8 | 9 | # Network 10 | np.random.seed(7) 11 | net = op.network.Cubic(shape=[51, 19, 1], spacing=1e-4, project=proj) 12 | 13 | # Geometry 14 | geo = op.geometry.SpheresAndCylinders(network=net, pores=net.Ps, throats=net.Ts) 15 | 16 | # Phase 17 | phase = op.phase.Water(network=net) 18 | 19 | # Physics 20 | phys = op.physics.GenericPhysics(network=net, phase=phase, geometry=geo) 21 | phase['pore.diffusivity'] = 2e-09 22 | phase['throat.diffusivity'] = 2e-09 23 | 24 | mod = op.models.physics.diffusive_conductance.ordinary_diffusion 25 | phys.add_model(propname='throat.diffusive_conductance', 26 | model=mod, regen_mode='normal') 27 | 28 | # Algorithms: Fickian diffusion 29 | fd = op.algorithms.TransientFickianDiffusion(network=net, phase=phase) 30 | fd.set_value_BC(pores=net.pores('front'), values=0.5) 31 | fd.set_value_BC(pores=net.pores('back'), values=0.1) 32 | fd.run(x0=0, tspan=(0, 100), saveat=10) 33 | phase.update(fd.results()) 34 | 35 | # Output results to a vtk file 36 | phase.update(fd.results()) 37 | proj.export_data(phases=[phase], filename='out', filetype='xdmf') 38 | -------------------------------------------------------------------------------- /scripts/tmp_comsol_data.npz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PMEAL/OpenPNM/7fd91d9d5accafd8ac58f343b4ce16bc143640fe/scripts/tmp_comsol_data.npz -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 3.5.0.dev2 3 | parse = (?P\d+)\.(?P\d+)\.(?P\d+)\.(?P\D+)(?P\d+)? 4 | serialize = {major}.{minor}.{patch}.{release}{build} 5 | 6 | [bumpversion:part:release] 7 | values = dev 8 | 9 | [bumpversion:part:build] 10 | first_value = 0 11 | 12 | [options] 13 | python_requires = >= 3.8 14 | 15 | [metadata] 16 | license_file = LICENSE 17 | -------------------------------------------------------------------------------- /tests/fixtures/3DMA-Castlegate/castle_cln.np2th: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PMEAL/OpenPNM/7fd91d9d5accafd8ac58f343b4ce16bc143640fe/tests/fixtures/3DMA-Castlegate/castle_cln.np2th -------------------------------------------------------------------------------- /tests/fixtures/3DMA-Castlegate/castle_cln.th2np: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PMEAL/OpenPNM/7fd91d9d5accafd8ac58f343b4ce16bc143640fe/tests/fixtures/3DMA-Castlegate/castle_cln.th2np -------------------------------------------------------------------------------- /tests/fixtures/JSONGraphFormat/invalid.json: -------------------------------------------------------------------------------- 1 | { 2 | "graph": { 3 | "nodes": [ 4 | { 5 | "id": 0 6 | } 7 | ] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/fixtures/JSONGraphFormat/valid.json: -------------------------------------------------------------------------------- 1 | { 2 | "graph": { 3 | "metadata": { 4 | "number_of_nodes": 2, 5 | "number_of_links": 1 6 | }, 7 | "nodes": [ 8 | { 9 | "id": "0", 10 | "metadata": { 11 | "node_squared_radius": 9, 12 | "node_coordinates": { 13 | "x": 0, 14 | "y": 0, 15 | "z": 0 16 | } 17 | } 18 | }, 19 | { 20 | "id": "1", 21 | "metadata": { 22 | "node_squared_radius": 4, 23 | "node_coordinates": { 24 | "x": 1, 25 | "y": 1, 26 | "z": 1 27 | } 28 | } 29 | } 30 | ], 31 | "edges": [ 32 | { 33 | "id": "0", 34 | "source": "0", 35 | "target": "1", 36 | "metadata": { 37 | "link_length": 1.73205080757, 38 | "link_squared_radius": 5.169298742047715 39 | } 40 | } 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/fixtures/OpenPNM-Objects/net_01.net: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PMEAL/OpenPNM/7fd91d9d5accafd8ac58f343b4ce16bc143640fe/tests/fixtures/OpenPNM-Objects/net_01.net -------------------------------------------------------------------------------- /tests/fixtures/PerGeos/mandatory.am: -------------------------------------------------------------------------------- 1 | # Avizo 3D ASCII 3.0 2 | 3 | 4 | define VERTEX 3 5 | define EDGE 3 6 | define POINT 6 7 | 8 | Parameters { 9 | ContentType "HxPoreNetworkModel" 10 | } 11 | 12 | VERTEX { float[3] VertexCoordinates } @1 13 | VERTEX { float EqRadius } @4 14 | EDGE { int[2] EdgeConnectivity } @6 15 | EDGE { int NumEdgePoints } @7 16 | POINT { float[3] EdgePointCoordinates } @11 17 | 18 | # Data section follows 19 | @1 20 | 3.099916750000000e+006 1.505000625000000e+006 3.054984750000000e+006 21 | 3.081143250000000e+006 1.471111250000000e+006 3.086930750000000e+006 22 | 3.137905250000000e+006 1.513538375000000e+006 3.082482500000000e+006 23 | 24 | @4 25 | 1.663660351562500e+004 26 | 1.150913769531250e+004 27 | 3.540010546875000e+004 28 | 29 | @6 30 | 2 0 31 | 1 0 32 | 2 1 33 | 34 | @7 35 | 2 36 | 2 37 | 2 38 | 39 | @11 40 | 3.137905250000000e+006 1.513538375000000e+006 3.082482500000000e+006 41 | 3.099916750000000e+006 1.505000625000000e+006 3.054984750000000e+006 42 | 3.081143250000000e+006 1.471111250000000e+006 3.086930750000000e+006 43 | 3.099916750000000e+006 1.505000625000000e+006 3.054984750000000e+006 44 | 3.137905250000000e+006 1.513538375000000e+006 3.082482500000000e+006 45 | 3.081143250000000e+006 1.471111250000000e+006 3.086930750000000e+006 46 | 47 | -------------------------------------------------------------------------------- /tests/fixtures/PerGeos/simplePNM.am: -------------------------------------------------------------------------------- 1 | # Avizo 3D ASCII 3.0 2 | 3 | 4 | define VERTEX 3 5 | define EDGE 3 6 | define POINT 6 7 | 8 | Parameters { 9 | ContentType "HxPoreNetworkModel" 10 | } 11 | 12 | VERTEX { float[3] VertexCoordinates } @1 13 | VERTEX { float Volume } @2 14 | VERTEX { float Area } @3 15 | VERTEX { float EqRadius } @4 16 | VERTEX { int LabelID } @5 17 | EDGE { int[2] EdgeConnectivity } @6 18 | EDGE { int NumEdgePoints } @7 19 | EDGE { float Area } @8 20 | EDGE { float EqRadius } @9 21 | EDGE { float ChannelLength } @10 22 | POINT { float[3] EdgePointCoordinates } @11 23 | 24 | # Data section follows 25 | @1 26 | 3.099916750000000e+006 1.505000625000000e+006 3.054984750000000e+006 27 | 3.081143250000000e+006 1.471111250000000e+006 3.086930750000000e+006 28 | 3.137905250000000e+006 1.513538375000000e+006 3.082482500000000e+006 29 | 30 | @2 31 | 1.928777747660800e+013 32 | 6.385817878528000e+012 33 | 1.858240417628160e+014 34 | 35 | @3 36 | 5.384168448000000e+009 37 | 3.166066432000000e+009 38 | 2.720774963200000e+010 39 | 40 | @4 41 | 1.663660351562500e+004 42 | 1.150913769531250e+004 43 | 3.540010546875000e+004 44 | 45 | @5 46 | 1 47 | 2 48 | 3 49 | 50 | @6 51 | 2 0 52 | 1 0 53 | 2 1 54 | 55 | @7 56 | 2 57 | 2 58 | 2 59 | 60 | @8 61 | 1.372888192000000e+009 62 | 2.792375400000000e+007 63 | 7.901805600000000e+007 64 | 65 | @9 66 | 2.090463867187500e+004 67 | 2.981343261718750e+003 68 | 5.015199707031250e+003 69 | 70 | @10 71 | 4.766702734375000e+004 72 | 5.021435156250000e+004 73 | 7.100543750000000e+004 74 | 75 | @11 76 | 3.137905250000000e+006 1.513538375000000e+006 3.082482500000000e+006 77 | 3.099916750000000e+006 1.505000625000000e+006 3.054984750000000e+006 78 | 3.081143250000000e+006 1.471111250000000e+006 3.086930750000000e+006 79 | 3.099916750000000e+006 1.505000625000000e+006 3.054984750000000e+006 80 | 3.137905250000000e+006 1.513538375000000e+006 3.082482500000000e+006 81 | 3.081143250000000e+006 1.471111250000000e+006 3.086930750000000e+006 82 | -------------------------------------------------------------------------------- /tests/fixtures/berea_100_to_300.npz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PMEAL/OpenPNM/7fd91d9d5accafd8ac58f343b4ce16bc143640fe/tests/fixtures/berea_100_to_300.npz -------------------------------------------------------------------------------- /tests/unit/algorithms/SolversTest.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import numpy as np 4 | import numpy.testing as nt 5 | import pytest 6 | 7 | import openpnm as op 8 | 9 | 10 | class SolversTest: 11 | 12 | def setup_class(self): 13 | np.random.seed(0) 14 | self.net = op.network.Cubic(shape=[3, 4, 5]) 15 | mods = op.models.collections.geometry.spheres_and_cylinders 16 | self.net.add_model_collection(mods) 17 | self.net.regenerate_models() 18 | self.phase = op.phase.Phase(network=self.net) 19 | self.phase['throat.conductance'] = np.linspace(1, 5, num=self.net.Nt) 20 | self.alg = op.algorithms.Transport(network=self.net, phase=self.phase) 21 | self.alg.settings._update({'quantity': 'pore.x', 22 | 'conductance': 'throat.conductance'}) 23 | self.alg.set_value_BC(pores=self.net.pores('front'), values=1) 24 | self.alg.set_value_BC(pores=self.net.pores('bottom'), values=0, mode='overwrite') 25 | 26 | def test_scipy_spsolve(self): 27 | solver = op.solvers.ScipySpsolve() 28 | self.alg.run(solver=solver) 29 | x = self.alg['pore.x'] 30 | nt.assert_allclose(x.mean(), 0.624134, rtol=1e-5) 31 | 32 | @pytest.mark.skipif(sys.platform == 'darwin', reason="Pardiso not available on arm64") 33 | def test_pardiso_spsolve(self): 34 | solver = op.solvers.PardisoSpsolve() 35 | self.alg.run(solver=solver) 36 | x = self.alg['pore.x'] 37 | nt.assert_allclose(x.mean(), 0.624134, rtol=1e-5) 38 | 39 | def test_scipy_cg(self): 40 | solver = op.solvers.ScipyCG() 41 | self.alg.run(solver=solver) 42 | x = self.alg['pore.x'] 43 | nt.assert_allclose(x.mean(), 0.624134, rtol=1e-5) 44 | 45 | def test_pyamg_ruge_stuben_solver(self): 46 | solver = op.solvers.PyamgRugeStubenSolver() 47 | self.alg.run(solver=solver) 48 | x = self.alg['pore.x'] 49 | nt.assert_allclose(x.mean(), 0.624134, rtol=1e-5) 50 | 51 | 52 | if __name__ == '__main__': 53 | t = SolversTest() 54 | t.setup_class() 55 | self = t 56 | for item in t.__dir__(): 57 | if item.startswith('test'): 58 | print(f'Running test: {item}') 59 | t.__getattribute__(item)() 60 | -------------------------------------------------------------------------------- /tests/unit/algorithms/TransientFickianDiffusionTest.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy.testing import assert_allclose 3 | import openpnm as op 4 | 5 | 6 | class TransientFickianDiffusionTest: 7 | 8 | def setup_class(self): 9 | np.random.seed(0) 10 | self.net = op.network.Cubic(shape=[4, 3, 1], spacing=1.0) 11 | self.net.add_model_collection( 12 | op.models.collections.geometry.spheres_and_cylinders) 13 | self.net.regenerate_models() 14 | self.net['pore.volume'] = 1e-14 15 | self.phase = op.phase.Phase(network=self.net) 16 | self.phase['pore.molar_density'] = 55500 17 | self.phase['throat.diffusive_conductance'] = 1e-15 18 | self.alg = op.algorithms.TransientFickianDiffusion(network=self.net, 19 | phase=self.phase) 20 | self.alg.settings._update({'quantity': 'pore.concentration', 21 | 'conductance': 'throat.diffusive_conductance'}) 22 | self.alg.set_value_BC(pores=self.net.pores('right'), values=1) 23 | self.alg.set_value_BC(pores=self.net.pores('left'), values=0) 24 | 25 | def test_transient_fickian_diffusion_intermediate_time(self): 26 | self.alg.run(x0=0, tspan=(0, 10)) 27 | desired = 0.40803 28 | actual = self.alg.x.mean() 29 | assert_allclose(actual, desired, rtol=1e-5) 30 | 31 | def test_transient_fickian_diffusion_steady_state(self): 32 | self.alg.run(x0=0, tspan=(0, 200)) # pick a relatively large tend 33 | desired = (1 + 0) / 2 # steady value = avearge of BCs 34 | actual = self.alg.x.mean() 35 | assert_allclose(actual, desired, rtol=1e-5) 36 | 37 | def teardown_class(self): 38 | ws = op.Workspace() 39 | ws.clear() 40 | 41 | 42 | if __name__ == '__main__': 43 | 44 | t = TransientFickianDiffusionTest() 45 | t.setup_class() 46 | for item in t.__dir__(): 47 | if item.startswith('test'): 48 | print(f'Running test: {item}') 49 | t.__getattribute__(item)() 50 | self = t 51 | -------------------------------------------------------------------------------- /tests/unit/core/HealthCheckTest.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | 4 | import openpnm as op 5 | 6 | 7 | class HealthCheckTest: 8 | 9 | def setup_class(self): 10 | self.ws = op.Workspace() 11 | self.ws.clear() 12 | self.net = op.network.Cubic(shape=[2, 2, 2]) 13 | 14 | def check_data_health(self): 15 | self.net.update({'pore.test': np.array([1, 2, 3, 4, 5, 6])}) 16 | a = op.utils.check_data_health(self.net) 17 | assert a.health == False 18 | assert a['pore.test'] != [] 19 | assert a['pore.coords'] == [] 20 | assert a['throat.conns'] == [] 21 | self.net['pore.test'] = 1.0 22 | self.net['pore.test'][0] = np.nan 23 | a = op.utils.check_data_health(self.net) 24 | assert a['pore.test'] != [] 25 | 26 | 27 | if __name__ == '__main__': 28 | 29 | t = HealthCheckTest() 30 | self = t 31 | t.setup_class() 32 | for item in t.__dir__(): 33 | if item.startswith('test'): 34 | print(f"Running test: {item}") 35 | t.__getattribute__(item)() 36 | -------------------------------------------------------------------------------- /tests/unit/core/ModelsWrapperTest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import openpnm as op 3 | import numpy as np 4 | 5 | 6 | class ModelsTest: 7 | 8 | def setup_class(self): 9 | self.net = op.network.Demo([4, 4, 1]) 10 | 11 | def test_call(self): 12 | a = self.net['pore.diameter'] 13 | b = self.net.models['pore.diameter@all']() 14 | assert np.all(a == b) 15 | 16 | def test_find_target(self): 17 | a = self.net.models['pore.diameter@all'].target 18 | assert a is self.net 19 | 20 | def test_name(self): 21 | a = 'pore.diameter@all' 22 | assert a == self.net.models['pore.diameter@all'].name 23 | 24 | def test_propname(self): 25 | a = 'pore.diameter' 26 | assert a == self.net.models['pore.diameter@all'].propname 27 | 28 | def test_domain(self): 29 | a = 'pore.all' 30 | assert a == self.net.models['pore.diameter@all'].domain 31 | 32 | def test_run_model(self): 33 | a = self.net['pore.seed'].copy() 34 | b = self.net['pore.diameter'].copy() 35 | self.net.run_model('pore.seed') 36 | c = self.net['pore.seed'].copy() 37 | d = self.net['pore.diameter'].copy() 38 | assert not np.any(a == c) 39 | assert np.all(b == d) 40 | self.net.run_model('pore.diameter') 41 | e = self.net['pore.diameter'].copy() 42 | assert not np.any(b == e) 43 | 44 | 45 | 46 | 47 | if __name__ == '__main__': 48 | 49 | t = ModelsTest() 50 | t.setup_class() 51 | self = t 52 | for item in t.__dir__(): 53 | if item.startswith('test'): 54 | print(f'Running test: {item}') 55 | t.__getattribute__(item)() 56 | -------------------------------------------------------------------------------- /tests/unit/io/COMSOLTest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | import openpnm as op 4 | import pytest 5 | 6 | 7 | class COMSOLTest: 8 | 9 | def setup_class(self): 10 | np.random.seed(10) 11 | self.net2d = op.network.Cubic(shape=[3, 4]) 12 | self.net2d["pore.diameter"] = np.random.rand(self.net2d.Np) 13 | self.net2d["throat.diameter"] = np.random.rand(self.net2d.Nt) 14 | self.net3d = op.network.Cubic(shape=[3, 4, 5]) 15 | self.net3d["pore.diameter"] = np.random.rand(self.net3d.Np) 16 | self.net3d["throat.diameter"] = np.random.rand(self.net3d.Nt) 17 | 18 | 19 | def teardown_class(self): 20 | os.remove(f"{self.net2d.name}.mphtxt") 21 | 22 | def test_export_data_2d_network(self): 23 | op.io.network_to_comsol(network=self.net2d) 24 | assert os.path.isfile(f"{self.net2d.name}.mphtxt") 25 | 26 | def test_export_data_3d_network(self): 27 | with pytest.raises(Exception): 28 | op.io.network_to_comsol(network=self.net3d) 29 | 30 | 31 | if __name__ == '__main__': 32 | import py 33 | # All the tests in this file can be run with 'playing' this file 34 | t = COMSOLTest() 35 | self = t # For interacting with the tests at the command line 36 | t.setup_class() 37 | for item in t.__dir__(): 38 | if item.startswith('test'): 39 | print(f'Running test: {item}') 40 | try: 41 | t.__getattribute__(item)() 42 | except TypeError: 43 | t.__getattribute__(item)(tmpdir=py.path.local()) 44 | t.teardown_class() 45 | -------------------------------------------------------------------------------- /tests/unit/io/DictTest.py: -------------------------------------------------------------------------------- 1 | import openpnm as op 2 | 3 | 4 | class DictTest: 5 | 6 | def setup_class(self): 7 | ws = op.Workspace() 8 | ws.settings['local_data'] = True 9 | self.net = op.network.Cubic(shape=[2, 2, 2]) 10 | 11 | def teardown_class(self): 12 | ws = op.Workspace() 13 | ws.clear() 14 | 15 | 16 | if __name__ == '__main__': 17 | import py 18 | t = DictTest() 19 | self = t 20 | t.setup_class() 21 | for item in t.__dir__(): 22 | if item.startswith('test'): 23 | print(f"Running test: {item}") 24 | try: 25 | t.__getattribute__(item)() 26 | except TypeError: 27 | t.__getattribute__(item)(tmpdir=py.path.local()) 28 | -------------------------------------------------------------------------------- /tests/unit/io/HDF5Test.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import numpy as np 4 | 5 | import openpnm as op 6 | 7 | 8 | class HDF5Test: 9 | 10 | def setup_class(self): 11 | ws = op.Workspace() 12 | ws.settings['local_data'] = True 13 | self.net = op.network.Cubic(shape=[2, 2, 2]) 14 | self.net['pore.boo'] = 1 15 | self.net['throat.boo'] = 1 16 | 17 | self.phase_1 = op.phase.Phase(network=self.net) 18 | self.phase_1['pore.bar'] = 2 19 | self.phase_1['throat.bar'] = 2 20 | self.phase_2 = op.phase.Phase(network=self.net) 21 | self.phase_2['pore.bar'] = 2 22 | self.phase_2['throat.bar'] = 2 23 | 24 | self.net['pore.object'] = np.ones(self.net.Np, dtype=object) 25 | 26 | def teardown_class(self): 27 | ws = op.Workspace() 28 | ws.clear() 29 | 30 | def test_project_to_hdf5(self, tmpdir): 31 | fname = tmpdir.join(self.net.project.name) 32 | f = op.io.project_to_hdf5(project=self.net.project, filename=fname) 33 | assert list(f.keys()) == [self.net.name, self.phase_1.name, self.phase_2.name] 34 | filename = f.filename 35 | f.close() 36 | os.remove(filename) 37 | 38 | def test_print_hdf5(self, tmpdir): 39 | fname = tmpdir.join(self.net.project.name) 40 | f = op.io.project_to_hdf5(project=self.net.project, filename=fname) 41 | op.io.print_hdf5(f) 42 | filename = f.filename 43 | f.close() 44 | os.remove(filename) 45 | 46 | 47 | if __name__ == '__main__': 48 | import py 49 | 50 | # All the tests in this file can be run with 'playing' this file 51 | t = HDF5Test() 52 | self = t # For interacting with the tests at the command line 53 | t.setup_class() 54 | for item in t.__dir__(): 55 | if item.startswith('test'): 56 | print(f"Running test: {item}") 57 | try: 58 | t.__getattribute__(item)() 59 | except TypeError: 60 | t.__getattribute__(item)(tmpdir=py.path.local()) 61 | -------------------------------------------------------------------------------- /tests/unit/io/MARockTest.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | 4 | import openpnm as op 5 | 6 | 7 | class MARockTest: 8 | 9 | def setup_class(self): 10 | ws = op.Workspace() 11 | ws.clear() 12 | 13 | def teardown_class(self): 14 | ws = op.Workspace() 15 | ws.clear() 16 | 17 | def test_load_MARock(self): 18 | path = Path(os.path.realpath(__file__), 19 | '../../../fixtures/3DMA-Castlegate') 20 | net = op.io.network_from_marock(filename=path) 21 | assert hasattr(net, 'conns') 22 | assert net.Np == 9915 23 | assert net.Nt == 21805 24 | a = {'pore.ID_number', 'pore.boundary_type', 'pore.coordination', 25 | 'pore.coords', 'pore.volume', 'throat.conns', 26 | 'throat.coords', 'throat.cross_sectional_area'} 27 | assert a.issubset(net.props()) 28 | 29 | 30 | if __name__ == '__main__': 31 | import py 32 | 33 | # All the tests in this file can be run with 'playing' this file 34 | t = MARockTest() 35 | self = t # For interacting with the tests at the command line 36 | t.setup_class() 37 | for item in t.__dir__(): 38 | if item.startswith('test'): 39 | print(f"Running test: {item}") 40 | t.__getattribute__(item)() 41 | -------------------------------------------------------------------------------- /tests/unit/io/PandasTest.py: -------------------------------------------------------------------------------- 1 | import openpnm as op 2 | import numpy as np 3 | 4 | 5 | class PandasTest: 6 | 7 | def setup_class(self): 8 | ws = op.Workspace() 9 | ws.settings['local_data'] = True 10 | self.net = op.network.Cubic(shape=[2, 2, 2], name='bob') 11 | self.net['pore.boo'] = 1 12 | self.net['throat.boo'] = 1 13 | self.phase_1 = op.phase.Phase(network=self.net) 14 | self.phase_1['pore.bar'] = 2 15 | self.phase_1['throat.bar'] = 2 16 | self.phase_2 = op.phase.Phase(network=self.net) 17 | self.phase_2['pore.bar'] = 2 18 | self.phase_2['throat.bar'] = 2 19 | self.phase_1['pore.baz'] = 11 20 | self.phase_1['throat.baz'] = 11 21 | self.phase_2['pore.baz'] = 12 22 | self.phase_2['throat.baz'] = 12 23 | 24 | def teardown_class(self): 25 | ws = op.Workspace() 26 | ws.clear() 27 | 28 | def test_project_to_dataframe_not_joined(self): 29 | df = op.io.project_to_pandas(project=self.net.project, 30 | join=False) 31 | assert len(df['pore'].keys()) == 27 32 | assert len(df['throat'].keys()) == 10 33 | 34 | def test_project_to_dataframe_joined(self): 35 | df = op.io.project_to_pandas(project=self.net.project, 36 | join=True) 37 | assert len(df.keys()) == 37 38 | assert np.isnan(df['bob.pore.coords[0]']).sum() > 0 39 | 40 | def test_network_to_dataframe(self): 41 | df = op.io.network_to_pandas(network=self.net) 42 | assert len(df.keys()) == 2 43 | assert len(df['pore'].keys()) == 17 44 | assert len(df['throat'].keys()) == 4 45 | 46 | 47 | if __name__ == '__main__': 48 | t = PandasTest() 49 | self = t 50 | t.setup_class() 51 | for item in t.__dir__(): 52 | if item.startswith('test'): 53 | print(f'Running test: {item}') 54 | t.__getattribute__(item)() 55 | -------------------------------------------------------------------------------- /tests/unit/io/ParaViewTest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from os.path import realpath 4 | from pathlib import Path 5 | import openpnm as op 6 | import pytest 7 | 8 | 9 | @pytest.mark.skip(reason="Installing paraview takes forever!") 10 | class ParaViewTest: 11 | 12 | def setup_class(self): 13 | self.path = os.path.dirname(os.path.abspath(sys.argv[0])) 14 | 15 | def test_export_data(self): 16 | pn = op.network.Cubic(shape=[30, 40]) 17 | _ = op.phase.Water(network=pn) 18 | op.io.project_to_vtk(project=pn.project, filename='test.vtp') 19 | op.io.project_to_paraview(project=pn.project, filename='test.vtp') 20 | os.remove('test.pvsm') 21 | os.remove('test.vtp') 22 | 23 | def test_open_paraview(self): 24 | path = Path(realpath(__file__), '../../../fixtures/VTK-VTP/test.pvsm') 25 | op.io.ParaView.open_paraview(filename=path) 26 | 27 | 28 | if __name__ == "__main__": 29 | t = ParaViewTest() 30 | self = t 31 | t.setup_class() 32 | for item in t.__dir__(): 33 | if item.startswith("test"): 34 | print(f"Running test: {item}") 35 | t.__getattribute__(item)() 36 | -------------------------------------------------------------------------------- /tests/unit/io/PerGeosTest.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | 4 | import openpnm as op 5 | 6 | 7 | class PerGeosTest: 8 | 9 | def setup_class(self): 10 | ws = op.Workspace() 11 | ws.clear() 12 | 13 | def teardown_class(self): 14 | ws = op.Workspace() 15 | ws.clear() 16 | 17 | def test_load_PerGeos_simple(self, tmpdir): 18 | path = Path(os.path.realpath(__file__), 19 | '../../../fixtures/PerGeos/simplePNM.am') 20 | network = op.io.network_from_pergeos(path) 21 | assert network.Np == 3 22 | assert network.Nt == 3 23 | 24 | def test_load_PerGeos_mandatory(self, tmpdir): 25 | path = Path(os.path.realpath(__file__), 26 | '../../../fixtures/PerGeos/mandatory.am') 27 | project = op.io.network_from_pergeos(path) 28 | network = project.network 29 | assert network.Np == 3 30 | assert network.Nt == 3 31 | 32 | def test_load_PerGeos_flooded(self, tmpdir): 33 | path = Path(os.path.realpath(__file__), 34 | '../../../fixtures/PerGeos/flooded.am') 35 | network = op.io.network_from_pergeos(path) 36 | assert network.Np == 225 37 | assert network.Nt == 301 38 | 39 | def test_save_PerGeos(self, tmpdir): 40 | net = op.network.Cubic(shape=[5, 5, 5]) 41 | fname = tmpdir.join(net.project.name) 42 | len_before = len(tmpdir.listdir()) 43 | op.io.network_to_pergeos(net, filename=fname) 44 | assert len(tmpdir.listdir()) == (len_before + 1) 45 | os.remove(fname.dirpath().join(net.project.name + '.am')) 46 | 47 | 48 | if __name__ == '__main__': 49 | import py 50 | 51 | # All the tests in this file can be run with 'playing' this file 52 | t = PerGeosTest() 53 | self = t # For interacting with the tests at the command line 54 | t.setup_class() 55 | for item in t.__dir__(): 56 | if item.startswith('test'): 57 | print(f"Running test: {item}") 58 | try: 59 | t.__getattribute__(item)() 60 | except TypeError: 61 | t.__getattribute__(item)(tmpdir=py.path.local()) 62 | -------------------------------------------------------------------------------- /tests/unit/io/PoreSpyTest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import openpnm as op 3 | from pathlib import Path 4 | import pickle 5 | 6 | 7 | class PoreSpyTest: 8 | 9 | def setup_class(self): 10 | self.path = os.path.join(Path(__file__).parent.resolve(), "berea.net") 11 | with open(self.path, 'rb') as f: 12 | self.net = pickle.load(f) 13 | 14 | def test_load_PoreSpy_from_pickle(self): 15 | proj = op.io.network_from_porespy(self.net) 16 | net = proj.network 17 | assert net.Np == 1637 18 | assert net.Nt == 2785 19 | 20 | def test_load_PoreSpy_from_file(self): 21 | proj = op.io.network_from_porespy(filename=self.path) 22 | net = proj.network 23 | assert net.Np == 1637 24 | assert net.Nt == 2785 25 | 26 | 27 | if __name__ == '__main__': 28 | # All the tests in this file can be run with 'playing' this file 29 | t = PoreSpyTest() 30 | self = t # For interacting with the tests at the command line 31 | t.setup_class() 32 | for item in t.__dir__(): 33 | if item.startswith('test'): 34 | print(f'Running test: {item}') 35 | t.__getattribute__(item)() 36 | -------------------------------------------------------------------------------- /tests/unit/io/STLTest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import platform 3 | 4 | import openpnm as op 5 | import py 6 | import pytest 7 | from openpnm.models.misc import from_neighbor_pores 8 | 9 | import numpy as np 10 | 11 | is_not_linux = platform.system() in ["Windows", "Darwin"] 12 | 13 | 14 | @pytest.mark.skipif(is_not_linux, reason="Skipping on Windows/macOS") 15 | class STLTest: 16 | def setup_class(self): 17 | np.random.seed(10) 18 | self.net = op.network.Cubic(shape=[2, 2, 2]) 19 | self.net["pore.diameter"] = 0.5 + np.random.rand(self.net.Np) * 0.5 20 | Dt = from_neighbor_pores(self.net, prop="pore.diameter") * 0.5 21 | self.net["throat.diameter"] = Dt 22 | self.net["throat.length"] = 1.0 23 | 24 | def teardown_class(self): 25 | os.remove(f"{self.net.name}.stl") 26 | os.remove("custom_stl.stl") 27 | 28 | def test_export_data_stl(self): 29 | op.io.network_to_stl(network=self.net) 30 | assert os.path.isfile(f"{self.net.name}.stl") 31 | op.io.network_to_stl(network=self.net, filename="custom_stl") 32 | assert os.path.isfile("custom_stl.stl") 33 | 34 | 35 | if __name__ == "__main__": 36 | 37 | # All the tests in this file can be run with 'playing' this file 38 | t = STLTest() 39 | self = t # For interacting with the tests at the command line 40 | t.setup_class() 41 | for item in t.__dir__(): 42 | if item.startswith("test"): 43 | print(f"Running test: {item}") 44 | try: 45 | t.__getattribute__(item)() 46 | except TypeError: 47 | t.__getattribute__(item)(tmpdir=py.path.local()) 48 | t.teardown_class() 49 | -------------------------------------------------------------------------------- /tests/unit/io/SalomeTest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | import openpnm as op 4 | from openpnm.models.misc import from_neighbor_pores 5 | 6 | 7 | class SalomeTest: 8 | 9 | def setup_class(self): 10 | np.random.seed(10) 11 | self.net = op.network.Cubic(shape=[2, 2, 2]) 12 | self.net["pore.diameter"] = 0.5 + np.random.rand(self.net.Np) * 0.5 13 | Dt = from_neighbor_pores(self.net, prop="pore.diameter") * 0.5 14 | self.net["throat.diameter"] = Dt 15 | self.net["throat.length"] = 1.0 16 | 17 | def teardown_class(self): 18 | os.remove(f"{self.net.name}.py") 19 | os.remove("salome_custom.py") 20 | 21 | def test_export_data_salome(self): 22 | op.io.network_to_salome(network=self.net) 23 | assert os.path.isfile(f"{self.net.name}.py") 24 | op.io.network_to_salome(network=self.net, filename="salome_custom") 25 | assert os.path.isfile("salome_custom.py") 26 | 27 | 28 | if __name__ == '__main__': 29 | import py 30 | # All the tests in this file can be run with 'playing' this file 31 | t = SalomeTest() 32 | self = t # For interacting with the tests at the command line 33 | t.setup_class() 34 | for item in t.__dir__(): 35 | if item.startswith('test'): 36 | print(f'Running test: {item}') 37 | try: 38 | t.__getattribute__(item)() 39 | except TypeError: 40 | t.__getattribute__(item)(tmpdir=py.path.local()) 41 | t.teardown_class() 42 | -------------------------------------------------------------------------------- /tests/unit/io/StatoilTest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | import scipy as sp 4 | import openpnm as op 5 | import networkx as nx 6 | from pathlib import Path 7 | 8 | 9 | class StatoilTest: 10 | 11 | def setup_class(self): 12 | ws = op.Workspace() 13 | ws.settings['local_data'] = True 14 | 15 | def teardown_class(self): 16 | ws = op.Workspace() 17 | ws.clear() 18 | 19 | def test_load_F42A(self, tmpdir): 20 | path = Path(os.path.realpath(__file__), 21 | '../../../fixtures/ICL-SandPack(F42A)') 22 | net = op.io.network_from_statoil(path=path.resolve(), prefix='F42A') 23 | self.net = net 24 | assert net.Np == 1246 25 | assert net.Nt == 2654 26 | assert np.shape(net['pore.coords']) == (1246, 3) 27 | assert np.shape(net['throat.conns']) == (2654, 2) 28 | assert 'pore.radius' in net.keys() 29 | 30 | def test_load_Berea(self, tmpdir): 31 | path = Path(os.path.realpath(__file__), 32 | '../../../fixtures/ICL-Sandstone(Berea)') 33 | net = op.io.network_from_statoil(path=path, prefix='Berea') 34 | assert net.Np == 6298 35 | assert net.Nt == 12098 36 | assert np.shape(net['pore.coords']) == (6298, 3) 37 | assert np.shape(net['throat.conns']) == (12098, 2) 38 | assert 'pore.radius' in net.keys() 39 | assert np.all(net.find_neighbor_pores(pores=1000) == [221, 1214]) 40 | 41 | 42 | if __name__ == '__main__': 43 | import py 44 | # All the tests in this file can be run with 'playing' this file 45 | t = StatoilTest() 46 | self = t # For interacting with the tests at the command line 47 | t.setup_class() 48 | for item in t.__dir__(): 49 | if item.startswith('test'): 50 | print('Running test: {item}') 51 | try: 52 | t.__getattribute__(item)() 53 | except TypeError: 54 | t.__getattribute__(item)(tmpdir=py.path.local()) 55 | -------------------------------------------------------------------------------- /tests/unit/io/XDMFTest.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import numpy as np 4 | 5 | import openpnm as op 6 | 7 | 8 | class XDMFTest: 9 | 10 | def setup_class(self): 11 | ws = op.Workspace() 12 | ws.settings['local_data'] = True 13 | self.net = op.network.Cubic(shape=[2, 2, 2]) 14 | self.net['pore.boo'] = 1 15 | self.net['throat.boo'] = 1 16 | 17 | self.phase_1 = op.phase.Phase(network=self.net) 18 | self.phase_1['pore.bar'] = 2 19 | self.phase_1['throat.bar'] = 2 20 | self.phase_2 = op.phase.Phase(network=self.net) 21 | self.phase_2['pore.bar'] = 2 22 | self.phase_2['throat.bar'] = 2 23 | # The follow is confirmation that bug 1456 is fixed 24 | self.phase_2['throat.blah.de.blah'] = 1 25 | self.phase_2['throat.blah.bloo'] = 1 26 | 27 | self.net['pore.object'] = np.ones(self.net.Np, dtype=object) 28 | 29 | def teardown_class(self): 30 | ws = op.Workspace() 31 | ws.clear() 32 | 33 | def test_save(self, tmpdir): 34 | fname = tmpdir.join('test_file') 35 | op.io.project_to_xdmf(self.net.project, filename=fname) 36 | os.remove(tmpdir.join('test_file.hdf')) 37 | os.remove(tmpdir.join('test_file.xmf')) 38 | 39 | 40 | if __name__ == '__main__': 41 | import py 42 | 43 | # All the tests in this file can be run with 'playing' this file 44 | t = XDMFTest() 45 | self = t # For interacting with the tests at the command line 46 | t.setup_class() 47 | for item in t.__dir__(): 48 | if item.startswith('test'): 49 | print(f"Running test: {item}") 50 | try: 51 | t.__getattribute__(item)() 52 | except TypeError: 53 | t.__getattribute__(item)(tmpdir=py.path.local()) 54 | -------------------------------------------------------------------------------- /tests/unit/io/berea.net: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PMEAL/OpenPNM/7fd91d9d5accafd8ac58f343b4ce16bc143640fe/tests/unit/io/berea.net -------------------------------------------------------------------------------- /tests/unit/io/custom_code.py: -------------------------------------------------------------------------------- 1 | def test(target): 2 | return 1.0 3 | -------------------------------------------------------------------------------- /tests/unit/models/geometry/PoreAreaTest.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy.testing import assert_approx_equal 3 | 4 | import openpnm as op 5 | import openpnm.models.geometry.pore_cross_sectional_area as mods 6 | 7 | 8 | class PoreAreaTest: 9 | def setup_class(self): 10 | self.net = op.network.Cubic(shape=[5, 5, 5], spacing=1.0) 11 | self.net.regenerate_models() 12 | self.net['pore.diameter'] = 1.0 13 | self.net['throat.cross_sectional_area'] = 0.1 14 | 15 | def test_sphere(self): 16 | self.net.add_model(propname='pore.area', 17 | model=mods.sphere, 18 | regen_mode='normal') 19 | a = np.array([0.78539816]) 20 | b = np.unique(self.net['pore.area']) 21 | assert_approx_equal(a, b) 22 | 23 | def test_cube(self): 24 | self.net.add_model(propname='pore.area', 25 | model=mods.cube, 26 | regen_mode='normal') 27 | a = np.array([1.0]) 28 | b = np.unique(self.net['pore.area']) 29 | assert_approx_equal(a, b) 30 | 31 | def test_circle(self): 32 | self.net.add_model(propname='pore.area', 33 | model=mods.circle, 34 | regen_mode='normal') 35 | a = np.array([1.0]) 36 | b = np.unique(self.net['pore.area']) 37 | assert_approx_equal(a, b) 38 | 39 | def test_square(self): 40 | self.net.add_model(propname='pore.area', 41 | model=mods.square, 42 | regen_mode='normal') 43 | a = np.array([1.0]) 44 | b = np.unique(self.net['pore.area']) 45 | assert_approx_equal(a, b) 46 | 47 | 48 | if __name__ == '__main__': 49 | 50 | t = PoreAreaTest() 51 | self = t 52 | t.setup_class() 53 | for item in t.__dir__(): 54 | if item.startswith('test'): 55 | print(f"Running test: {item}") 56 | t.__getattribute__(item)() 57 | -------------------------------------------------------------------------------- /tests/unit/models/geometry/PoreCentroidTest.py: -------------------------------------------------------------------------------- 1 | class PoreCentroidTest: 2 | def test_voronoi(self): 3 | pass 4 | -------------------------------------------------------------------------------- /tests/unit/models/geometry/PoreSeedTest.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import scipy as sp 3 | 4 | import openpnm as op 5 | import openpnm.models.geometry.pore_seed as mods 6 | 7 | 8 | class PoreSeedTest: 9 | def setup_class(self): 10 | self.net = op.network.Cubic(shape=[5, 5, 5]) 11 | 12 | def test_random(self): 13 | f = mods.random 14 | self.net.add_model(propname='pore.seed', 15 | model=f, 16 | seed=0, 17 | num_range=[0.1, 2]) 18 | assert np.amax(self.net['pore.seed']) > 1 19 | assert np.amin(self.net['pore.seed']) < 1 20 | 21 | def test_spatially_correlated(self): 22 | f = mods.spatially_correlated 23 | self.net.add_model(propname='pore.seed', 24 | model=f, 25 | weights=[2, 2, 2], 26 | regen_mode='normal') 27 | assert np.amin(self.net['pore.seed'] > 0) 28 | assert np.amax(self.net['pore.seed'] < 1) 29 | 30 | def test_spatially_correlated_zero_weights(self): 31 | f = mods.spatially_correlated 32 | self.net.add_model(propname='pore.seed', 33 | model=f, 34 | weights=[0, 0, 0], 35 | regen_mode='normal') 36 | assert np.amin(self.net['pore.seed'] > 0) 37 | assert np.amax(self.net['pore.seed'] < 1) 38 | 39 | 40 | if __name__ == '__main__': 41 | 42 | t = PoreSeedTest() 43 | self = t 44 | t.setup_class() 45 | for item in t.__dir__(): 46 | if item.startswith('test'): 47 | print(f"Running test: {item}") 48 | t.__getattribute__(item)() 49 | -------------------------------------------------------------------------------- /tests/unit/models/geometry/ThroatCapillaryShapeFactorTest.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import openpnm as op 3 | 4 | 5 | class ThroatCapillaryShapeFactorTest: 6 | 7 | def setup_class(self): 8 | self.net = op.network.Cubic(shape=[5, 5, 5], spacing=1.0) 9 | self.air = op.phase.Air(network=self.net) 10 | self.net['throat.cross_sectional_area'] = 1.0 11 | self.net['throat.perimeter'] = np.pi 12 | self.net['throat.diameter'] = (4 / np.pi)**(0.5) 13 | 14 | def test_mason_morrow(self): 15 | mod = op.models.geometry.throat_capillary_shape_factor.mason_morrow 16 | self.net.add_model(propname='throat.capillary_shape_factor', 17 | model=mod, 18 | throat_perimeter='throat.perimeter', 19 | throat_area='throat.cross_sectional_area', 20 | regen_mode='normal') 21 | a = np.unique(self.net['throat.capillary_shape_factor']) 22 | b = np.array(0.10132118, ndmin=1) 23 | assert np.allclose(a, b) 24 | 25 | def test_jenkins_rao(self): 26 | mod = op.models.geometry.throat_capillary_shape_factor.jenkins_rao 27 | self.net.add_model(propname='throat.capillary_shape_factor', 28 | model=mod, 29 | throat_perimeter='throat.perimeter', 30 | throat_area='throat.cross_sectional_area', 31 | throat_diameter='throat.diameter', 32 | regen_mode='normal') 33 | a = np.unique(self.net['throat.capillary_shape_factor']) 34 | b = np.array(0.88622693, ndmin=1) 35 | assert np.allclose(a, b) 36 | 37 | 38 | if __name__ == '__main__': 39 | 40 | t = ThroatCapillaryShapeFactorTest() 41 | self = t 42 | t.setup_class() 43 | for item in t.__dir__(): 44 | if item.startswith('test'): 45 | print(f'Running test: {item}') 46 | t.__getattribute__(item)() 47 | -------------------------------------------------------------------------------- /tests/unit/models/geometry/ThroatCentroidTest.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import openpnm as op 4 | import openpnm.models.geometry as gm 5 | 6 | 7 | class ThroatCentroidTest: 8 | 9 | def setup_class(self): 10 | self.net = op.network.Cubic(shape=[5, 1, 1], spacing=1) 11 | 12 | def test_voronoi(self): 13 | pass 14 | 15 | def test_pore_coords(self): 16 | self.net.add_model(propname='throat.centroid', 17 | model=gm.throat_centroid.pore_coords) 18 | a = np.arange(1, 5, 1) 19 | assert np.allclose(a, self.net['throat.centroid'][:, 0]) 20 | assert np.all(self.net['throat.centroid'][:, 1] == 0.5) 21 | assert np.all(self.net['throat.centroid'][:, 2] == 0.5) 22 | 23 | 24 | if __name__ == '__main__': 25 | 26 | t = ThroatCentroidTest() 27 | self = t 28 | t.setup_class() 29 | for item in t.__dir__(): 30 | if item.startswith('test'): 31 | print(f"Running test: {item}") 32 | t.__getattribute__(item)() 33 | -------------------------------------------------------------------------------- /tests/unit/models/geometry/ThroatCrossSectionalAreaTest.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy.testing import assert_approx_equal 3 | 4 | import openpnm as op 5 | import openpnm.models.geometry.throat_cross_sectional_area as mods 6 | 7 | 8 | class ThroatCrossSectionalAreaTest: 9 | def setup_class(self): 10 | self.net = op.network.Cubic(shape=[5, 5, 5], spacing=1.0) 11 | self.net['pore.diameter'] = 0.2 12 | self.net['throat.length'] = 1 13 | self.net['throat.diameter'] = 0.1 14 | 15 | def test_sphere(self): 16 | self.net.add_model(propname='throat.cross_sectional_area', 17 | model=mods.cylinder, 18 | regen_mode='normal') 19 | a = np.array([0.007853981]) 20 | b = np.unique(self.net['throat.cross_sectional_area']) 21 | assert_approx_equal(a, b) 22 | 23 | def test_cube(self): 24 | self.net.add_model(propname='throat.cross_sectional_area', 25 | model=mods.cuboid, 26 | regen_mode='normal') 27 | a = np.array([0.01]) 28 | b = np.unique(self.net['throat.cross_sectional_area']) 29 | assert_approx_equal(a, b) 30 | 31 | def test_rectangle(self): 32 | self.net.add_model(propname='throat.cross_sectional_area', 33 | model=mods.rectangle, 34 | regen_mode='normal') 35 | a = np.array([0.1]) 36 | b = np.unique(self.net['throat.cross_sectional_area']) 37 | assert_approx_equal(a, b) 38 | 39 | 40 | if __name__ == '__main__': 41 | 42 | t = ThroatCrossSectionalAreaTest() 43 | self = t 44 | t.setup_class() 45 | for item in t.__dir__(): 46 | if item.startswith('test'): 47 | print(f"Running test: {item}") 48 | t.__getattribute__(item)() 49 | -------------------------------------------------------------------------------- /tests/unit/models/geometry/ThroatEndpointsTest.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy.testing import assert_array_almost_equal 3 | 4 | import openpnm as op 5 | import openpnm.models.geometry as gm 6 | 7 | 8 | class ThroatEndpointsTest: 9 | 10 | def setup_class(self): 11 | self.net = op.network.Demo(shape=[5, 1, 1], spacing=1) 12 | 13 | def test_spheres_and_cylinders(self): 14 | self.net.add_model( 15 | propname='throat.endpoints', 16 | model=op.models.geometry.throat_endpoints.spheres_and_cylinders 17 | ) 18 | delta = (self.net['throat.endpoints']['tail'] 19 | - self.net['throat.endpoints']['head'])[:, 0] 20 | self.net.add_model(propname='throat.length', 21 | model=gm.throat_length.circles_and_rectangles) 22 | assert_array_almost_equal(delta, self.net['throat.length'], decimal=10) 23 | 24 | 25 | if __name__ == '__main__': 26 | 27 | t = ThroatEndpointsTest() 28 | self = t 29 | t.setup_class() 30 | for item in t.__dir__(): 31 | if item.startswith('test'): 32 | print(f"Running test: {item}") 33 | t.__getattribute__(item)() 34 | -------------------------------------------------------------------------------- /tests/unit/models/geometry/ThroatNormalTest.py: -------------------------------------------------------------------------------- 1 | class ThroatNormalTest: 2 | def test_voronoi(self): 3 | pass 4 | -------------------------------------------------------------------------------- /tests/unit/models/geometry/ThroatOffsetVerticesTest.py: -------------------------------------------------------------------------------- 1 | class ThroatOffsetVerticesTest: 2 | def test_voronoi(self): 3 | pass 4 | -------------------------------------------------------------------------------- /tests/unit/models/geometry/ThroatPerimeterTest.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy.testing import assert_approx_equal 3 | 4 | import openpnm as op 5 | import openpnm.models.geometry.throat_perimeter as mods 6 | 7 | 8 | class ThroatPerimeterTest: 9 | def setup_class(self): 10 | self.net = op.network.Cubic(shape=[5, 5, 5], spacing=1.0) 11 | self.net['throat.diameter'] = 0.1 12 | 13 | def test_cylinder(self): 14 | self.net.add_model(propname='throat.perimeter', 15 | model=mods.cylinder, 16 | regen_mode='normal') 17 | a = np.array([0.31415927]) 18 | b = np.unique(self.net['throat.perimeter']) 19 | assert_approx_equal(a, b) 20 | 21 | def test_cuboid(self): 22 | self.net.add_model(propname='throat.perimeter', 23 | model=mods.cuboid, 24 | regen_mode='normal') 25 | a = np.array([0.4]) 26 | b = np.unique(self.net['throat.perimeter']) 27 | assert_approx_equal(a, b) 28 | 29 | def test_rectangle(self): 30 | self.net.add_model(propname='throat.perimeter', 31 | model=mods.rectangle, 32 | regen_mode='normal') 33 | a = np.array([1.0]) 34 | b = np.unique(self.net['throat.perimeter']) 35 | assert_approx_equal(a, b) 36 | 37 | 38 | if __name__ == '__main__': 39 | 40 | t = ThroatPerimeterTest() 41 | self = t 42 | t.setup_class() 43 | for item in t.__dir__(): 44 | if item.startswith('test'): 45 | print(f"Running test: {item}") 46 | t.__getattribute__(item)() 47 | -------------------------------------------------------------------------------- /tests/unit/models/geometry/ThroatSeedTest.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import openpnm as op 4 | import openpnm.models.geometry.throat_seed as mods 5 | 6 | 7 | class ThroatSeedTest: 8 | def setup_class(self): 9 | self.net = op.network.Cubic(shape=[5, 5, 5]) 10 | self.net['pore.seed'] = np.random.rand(self.net.Np) 11 | 12 | def test_random(self): 13 | self.net.add_model(propname='throat.seed', 14 | model=mods.random, 15 | seed=0, 16 | num_range=[0.1, 2]) 17 | self.net.regenerate_models() 18 | assert np.amax(self.net['throat.seed']) > 1.9 19 | assert np.amin(self.net['throat.seed']) > 0.1 20 | 21 | def test_neighbor(self): 22 | self.net.add_model(propname='throat.seed_max', 23 | model=mods.from_neighbor_pores, 24 | mode='max') 25 | self.net.add_model(propname='throat.seed_min', 26 | model=mods.from_neighbor_pores, 27 | mode='min') 28 | self.net.add_model(propname='throat.seed_mean', 29 | model=mods.from_neighbor_pores, 30 | mode='mean') 31 | self.net.regenerate_models() 32 | assert np.all(self.net['throat.seed_min'] <= self.net['throat.seed_max']) 33 | assert np.all(self.net['throat.seed_min'] <= self.net['throat.seed_mean']) 34 | assert np.all(self.net['throat.seed_mean'] <= self.net['throat.seed_max']) 35 | 36 | 37 | if __name__ == '__main__': 38 | 39 | t = ThroatSeedTest() 40 | self = t 41 | t.setup_class() 42 | for item in t.__dir__(): 43 | if item.startswith('test'): 44 | print(f"Running test: {item}") 45 | t.__getattribute__(item)() 46 | -------------------------------------------------------------------------------- /tests/unit/models/geometry/ThroatSurfaceAreaTest.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import scipy as sp 3 | from numpy.testing import assert_allclose 4 | 5 | import openpnm as op 6 | import openpnm.models.geometry.throat_surface_area as tsa 7 | 8 | 9 | class ThroatSurfaceAreaTest: 10 | def setup_class(self): 11 | self.net = op.network.Cubic(shape=[5, 5, 5]) 12 | self.net['throat.diameter'] = np.ones((self.net.Nt, )) 13 | self.net['throat.length'] = np.ones((self.net.Nt, )) 14 | self.net['throat.perimeter'] = np.ones((self.net.Nt, )) 15 | 16 | def test_cylinder(self): 17 | self.net.add_model(propname='throat.surface_area', 18 | model=tsa.cylinder, regen_mode="normal") 19 | assert_allclose(self.net['throat.surface_area'].mean(), np.pi) 20 | 21 | def test_cuboid(self): 22 | self.net.add_model(propname='throat.surface_area', 23 | model=tsa.cuboid, regen_mode="normal") 24 | assert_allclose(self.net['throat.surface_area'].mean(), 4) 25 | 26 | def test_rectangle(self): 27 | self.net.add_model(propname='throat.surface_area', 28 | model=tsa.rectangle, regen_mode="normal") 29 | assert_allclose(self.net['throat.surface_area'].mean(), 2) 30 | 31 | def test_extrusion(self): 32 | self.net.add_model(propname='throat.surface_area', 33 | model=tsa.extrusion, regen_mode="normal") 34 | assert_allclose(self.net['throat.surface_area'].mean(), 1) 35 | 36 | 37 | if __name__ == '__main__': 38 | 39 | t = ThroatSurfaceAreaTest() 40 | self = t 41 | t.setup_class() 42 | for item in t.__dir__(): 43 | if item.startswith('test'): 44 | print(f"Running test: {item}") 45 | t.__getattribute__(item)() 46 | -------------------------------------------------------------------------------- /tests/unit/models/geometry/ThroatVectorTest.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import openpnm as op 4 | import openpnm.models.geometry as gm 5 | 6 | 7 | class ThroatVectorTest: 8 | 9 | def setup_class(self): 10 | self.net = op.network.Cubic(shape=[5, 1, 1], spacing=1) 11 | 12 | def test_pore_to_pore(self): 13 | self.net.add_model(propname='throat.vector', 14 | model=gm.throat_vector.pore_to_pore) 15 | assert np.all(self.net['throat.vector'][:, 0] == 1.0) 16 | assert np.all(self.net['throat.vector'][:, 1] == 0.0) 17 | assert np.all(self.net['throat.vector'][:, 2] == 0.0) 18 | 19 | 20 | if __name__ == '__main__': 21 | 22 | t = ThroatVectorTest() 23 | self = t 24 | t.setup_class() 25 | for item in t.__dir__(): 26 | if item.startswith('test'): 27 | print(f"Running test: {item}") 28 | t.__getattribute__(item)() 29 | -------------------------------------------------------------------------------- /tests/unit/models/phase/DiffusivityTest.py: -------------------------------------------------------------------------------- 1 | from numpy.testing import assert_approx_equal 2 | 3 | import openpnm as op 4 | 5 | 6 | class DiffusivityTest: 7 | def setup_class(self): 8 | self.net = op.network.Cubic(shape=[3, 3, 3]) 9 | self.phase = op.phase.Phase(network=self.net) 10 | self.phase['pore.temperature'] = 298.0 # K 11 | self.phase['pore.pressure'] = 101325 # Pa 12 | self.phase['pore.viscosity'] = 1.75e-5 # Pa.s 13 | 14 | def test_fuller(self): 15 | f = op.models.phase.diffusivity.gas_mixture_fesg 16 | self.phase.add_model(propname='pore.diffusivity', 17 | model=f, 18 | MWs=[31.9988, 28.0134], 19 | Vdms=[16.6, 17.9],) 20 | self.phase.regenerate_models() 21 | assert_approx_equal(self.phase['pore.diffusivity'][0], 2.094693935e-05) 22 | 23 | 24 | if __name__ == '__main__': 25 | 26 | t = DiffusivityTest() 27 | self = t 28 | t.setup_class() 29 | for item in t.__dir__(): 30 | if item.startswith('test'): 31 | print(f"Running test: {item}") 32 | t.__getattribute__(item)() 33 | -------------------------------------------------------------------------------- /tests/unit/models/phase/LatentHeatOfVaporizationTest.py: -------------------------------------------------------------------------------- 1 | import chemicals 2 | from numpy.testing import assert_allclose 3 | from thermo import Chemical 4 | 5 | import openpnm as op 6 | 7 | 8 | class LatentHeatTest: 9 | def setup_class(self): 10 | self.net = op.network.Cubic(shape=[3, 3, 3]) 11 | 12 | def test_generic_chemicals_for_pure_liq(self): 13 | mods = [ 14 | chemicals.phase_change.MK, 15 | chemicals.phase_change.SMK, 16 | chemicals.phase_change.Velasco, 17 | chemicals.phase_change.Clapeyron, 18 | chemicals.phase_change.Riedel, 19 | chemicals.phase_change.Chen, 20 | # chemicals.phase_change.Vetere, # Unknown error 21 | chemicals.phase_change.Liu, 22 | # chemicals.phase_change.Watson, # Needs Hvap_ref 23 | ] 24 | h2o = op.phase.Species(network=self.net, species='water') 25 | a = Chemical('h2o') 26 | vals = [] 27 | for f in mods: 28 | vals.append(op.models.phase.chemicals_wrapper(phase=h2o, f=f).mean()) 29 | assert_allclose(vals, a.Hvapm, rtol=0.5) 30 | 31 | 32 | if __name__ == '__main__': 33 | 34 | t = LatentHeatTest() 35 | self = t 36 | t.setup_class() 37 | for item in t.__dir__(): 38 | if item.startswith('test'): 39 | print(f"Running test: {item}") 40 | t.__getattribute__(item)() 41 | -------------------------------------------------------------------------------- /tests/unit/models/physics/ElectricalConductanceTest.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy.testing import assert_allclose 3 | 4 | import openpnm as op 5 | 6 | 7 | class ElectricalConductanceTest: 8 | 9 | def setup_class(self): 10 | self.net = op.network.Cubic(shape=[4, 4, 4]) 11 | self.net['pore.diameter'] = 1.0 12 | self.net['throat.diameter'] = 0.5 13 | self.net['throat.diffusive_size_factors'] = \ 14 | np.ones([self.net.Nt, 3])*(0.123, 0.981, 0.551) 15 | self.phase = op.phase.Phase(network=self.net) 16 | self.phase['pore.electrical_conductivity'] = 1.0 17 | 18 | def test_generic_electrical(self): 19 | mod = op.models.physics.electrical_conductance.generic_electrical 20 | self.phase.add_model(propname='throat.electrical_conductance', model=mod) 21 | self.phase.regenerate_models() 22 | actual = np.mean(self.phase['throat.electrical_conductance']) 23 | assert_allclose(actual, desired=0.091205, rtol=1e-5) 24 | 25 | def test_series_resistors(self): 26 | mod = op.models.physics.electrical_conductance.series_resistors 27 | self.phase.add_model(propname='throat.electrical_conductance', model=mod) 28 | self.phase.regenerate_models() 29 | actual = np.mean(self.phase['throat.electrical_conductance']) 30 | assert_allclose(actual, desired=0.091205, rtol=1e-5) 31 | 32 | 33 | if __name__ == '__main__': 34 | 35 | t = ElectricalConductanceTest() 36 | self = t 37 | t.setup_class() 38 | for item in t.__dir__(): 39 | if item.startswith('test'): 40 | print(f"Running test: {item}") 41 | t.__getattribute__(item)() 42 | -------------------------------------------------------------------------------- /tests/unit/models/physics/ThermalConductanceTest.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy.testing import assert_allclose 3 | 4 | import openpnm as op 5 | 6 | 7 | class ThermalConductanceTest: 8 | 9 | def setup_class(self): 10 | self.net = op.network.Cubic(shape=[4, 4, 4]) 11 | self.net['throat.diffusive_size_factors'] = \ 12 | np.ones([self.net.Nt, 3])*(0.4, 0.2, 0.3) 13 | self.phase = op.phase.Phase(network=self.net) 14 | self.phase['pore.thermal_conductivity'] = 0.5 15 | 16 | def test_generic_thermal(self): 17 | mod = op.models.physics.thermal_conductance.generic_thermal 18 | self.phase.add_model(propname='throat.thermal_conductance', model=mod) 19 | self.phase.regenerate_models() 20 | actual = self.phase['throat.thermal_conductance'].mean() 21 | desired = 1 / (1/(0.4*0.5) + 1/(0.2*0.5) + 1/(0.3*0.5)) 22 | assert_allclose(actual, desired) 23 | 24 | def test_series_resistors(self): 25 | mod = op.models.physics.thermal_conductance.series_resistors 26 | self.phase.add_model(propname='throat.thermal_conductance', model=mod) 27 | self.phase.regenerate_models() 28 | actual = self.phase['throat.thermal_conductance'].mean() 29 | desired = 1 / (1/(0.4*0.5) + 1/(0.2*0.5) + 1/(0.3*0.5)) 30 | assert_allclose(actual, desired) 31 | 32 | 33 | if __name__ == '__main__': 34 | 35 | t = ThermalConductanceTest() 36 | self = t 37 | t.setup_class() 38 | for item in t.__dir__(): 39 | if item.startswith('test'): 40 | print(f"Running test: {item}") 41 | t.__getattribute__(item)() 42 | -------------------------------------------------------------------------------- /tests/unit/network/CubicTemplateTest.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import openpnm as op 3 | from skimage.morphology import ball, disk 4 | 5 | 6 | class CubicTemplateTest: 7 | def setup_class(self): 8 | pass 9 | 10 | def teardown_class(self): 11 | pass 12 | 13 | def test_2D_template(self): 14 | net = op.network.CubicTemplate(template=disk(10), spacing=1) 15 | assert net.Np == 317 16 | assert net.Nt == 592 17 | 18 | def test_3D_template(self): 19 | net = op.network.CubicTemplate(template=ball(5), spacing=1) 20 | assert net.Np == 515 21 | assert net.Nt == 1302 22 | 23 | def test_labels(self): 24 | template = np.array( 25 | [[1, 1, 1, 1, 1], 26 | [1, 1, 0, 1, 1], 27 | [1, 1, 0, 0, 1], 28 | [1, 0, 0, 0, 1], 29 | [1, 1, 0, 1, 1]] 30 | ) 31 | net = op.network.CubicTemplate(template=template, label_surface_pores=True) 32 | # Test "surface" label 33 | Ps_surf_desired = np.array([0, 1, 2, 3, 4, 5, 8, 9, 11, 12, 13, 14, 15, 16, 17]) 34 | Ps_surf = net.pores("surface") 35 | np.testing.assert_allclose(Ps_surf, Ps_surf_desired) 36 | # Test "internal_surface" label 37 | Ps_int_surf_desired = np.array([6, 7, 10]) 38 | Ps_int_surf = net.pores("internal_surface") 39 | np.testing.assert_allclose(Ps_int_surf, Ps_int_surf_desired) 40 | 41 | 42 | if __name__ == '__main__': 43 | 44 | t = CubicTemplateTest() 45 | t.setup_class() 46 | self = t 47 | for item in t.__dir__(): 48 | if item.startswith('test'): 49 | print(f'Running test: {item}') 50 | t.__getattribute__(item)() 51 | -------------------------------------------------------------------------------- /tests/unit/network/LabelTest.py: -------------------------------------------------------------------------------- 1 | import openpnm as op 2 | ws = op.Workspace() 3 | 4 | 5 | class LabelTest: 6 | def setup_class(self): 7 | pass 8 | 9 | def teardown_class(self): 10 | ws.clear() 11 | 12 | def test_bravais_fcc(self): 13 | net = op.network.FaceCenteredCubic(shape=[4, 4, 4]) 14 | assert 'pore.surface' in net.keys() 15 | assert net.num_pores('left') == 25 16 | assert net.num_pores(['left', 'top'], mode='xnor') == 4 17 | 18 | def test_bravais_bcc(self): 19 | net = op.network.BodyCenteredCubic(shape=[4, 4, 4]) 20 | assert 'pore.surface' in net.keys() 21 | assert net.num_pores('left') == 16 22 | assert net.num_pores(['left', 'top'], mode='xnor') == 4 23 | 24 | def test_bravais_sc(self): 25 | net = op.network.Cubic(shape=[4, 4, 4]) 26 | assert 'pore.surface' in net.keys() 27 | assert net.num_pores('left') == 16 28 | assert net.num_pores(['left', 'top'], mode='xnor') == 4 29 | 30 | def test_cubic_2d(self): 31 | net = op.network.Cubic(shape=[5, 5, 1]) 32 | assert 'pore.top' not in net.labels() 33 | assert 'pore.bottom' not in net.labels() 34 | assert net.num_pores('surface') == 16 35 | net = op.network.Cubic(shape=[5, 1, 5]) 36 | assert 'pore.front' not in net.labels() 37 | assert 'pore.back' not in net.labels() 38 | assert net.num_pores('surface') == 16 39 | net = op.network.Cubic(shape=[1, 5, 5]) 40 | assert 'pore.left' not in net.labels() 41 | assert 'pore.right' not in net.labels() 42 | assert net.num_pores('surface') == 16 43 | 44 | 45 | if __name__ == '__main__': 46 | 47 | t = LabelTest() 48 | t.setup_class() 49 | self = t 50 | for item in t.__dir__(): 51 | if item.startswith('test'): 52 | print(f'Running test: {item}') 53 | t.__getattribute__(item)() 54 | -------------------------------------------------------------------------------- /tests/unit/phase/AirTest.py: -------------------------------------------------------------------------------- 1 | import numpy.testing as nptest 2 | 3 | import openpnm as op 4 | 5 | 6 | class AirTest: 7 | def setup_class(self): 8 | self.net = op.network.Cubic(shape=[5, 5, 5]) 9 | self.phase = op.phase.Air(network=self.net) 10 | 11 | def test_generic_prop_with_temperature(self): 12 | T1 = 298.0 13 | T2 = 333.0 14 | vals = { 15 | 'pore.viscosity': 16 | { 17 | T1: 1.8444445170912796e-05, 18 | T2: 2.00725794239133e-05, 19 | }, 20 | 'pore.density': 21 | { 22 | T1: 1.1798234085231065, 23 | T2: 1.0558179451648222, 24 | }, 25 | 'pore.thermal_conductivity': 26 | { 27 | T1: 0.026369425206800003, 28 | T2: 0.028787674351300006, 29 | }, 30 | 'pore.diffusivity': 31 | { 32 | T1: 2.0946939351898755e-05, 33 | T2: 2.5440141454852606e-05, 34 | }, 35 | 'pore.molar_density': 36 | { 37 | T1: 40.89461870781484, 38 | T2: 36.596385510296765, 39 | }, 40 | } 41 | for prop in vals.keys(): 42 | print(f'Testing {prop}') 43 | self.phase['pore.temperature'] = T1 44 | self.phase.regenerate_models() 45 | val = self.phase[prop][0] 46 | nptest.assert_allclose(val, vals[prop][T1], rtol=1e-10) 47 | self.phase['pore.temperature'] = T2 48 | self.phase.regenerate_models() 49 | val = self.phase[prop][0] 50 | nptest.assert_allclose(val, vals[prop][T2], rtol=1e-10) 51 | 52 | 53 | if __name__ == '__main__': 54 | 55 | t = AirTest() 56 | self = t 57 | t.setup_class() 58 | for item in t.__dir__(): 59 | if item.startswith('test'): 60 | print(f"Running test: {item}") 61 | t.__getattribute__(item)() 62 | -------------------------------------------------------------------------------- /tests/unit/phase/MercuryTest.py: -------------------------------------------------------------------------------- 1 | import numpy.testing as nptest 2 | 3 | import openpnm as op 4 | 5 | 6 | class MercuryTest: 7 | def setup_class(self): 8 | self.net = op.network.Cubic(shape=[5, 5, 5]) 9 | self.phase = op.phase.Mercury(network=self.net) 10 | 11 | def test_generic_prop_with_temperature(self): 12 | T1 = 298.0 13 | T2 = 333.0 14 | vals = { 15 | 'pore.viscosity': 16 | { 17 | T1: 0.0015360551647071996, 18 | T2: 0.0014046786779692, 19 | }, 20 | 'pore.density': 21 | { 22 | T1: 13544.82808, 23 | T2: 13458.37668, 24 | }, 25 | 'pore.thermal_conductivity': 26 | { 27 | T1: 8.514606495199999, 28 | T2: 8.9719517682, 29 | }, 30 | 'pore.molar_density': 31 | { 32 | T1: 67524.9418216262, 33 | T2: 67093.95622912409, 34 | }, 35 | 'pore.surface_tension': 36 | { 37 | T1: 0.4791000000000001, 38 | T2: 0.46930000000000005, 39 | }, 40 | } 41 | for prop in vals.keys(): 42 | print(f'Testing {prop}') 43 | self.phase['pore.temperature'] = T1 44 | self.phase.regenerate_models() 45 | val = self.phase[prop][0] 46 | nptest.assert_array_almost_equal_nulp(val, vals[prop][T1]) 47 | self.phase['pore.temperature'] = T2 48 | self.phase.regenerate_models() 49 | val = self.phase[prop][0] 50 | nptest.assert_array_almost_equal_nulp(val, vals[prop][T2]) 51 | 52 | 53 | if __name__ == '__main__': 54 | 55 | t = MercuryTest() 56 | self = t 57 | t.setup_class() 58 | for item in t.__dir__(): 59 | if item.startswith('test'): 60 | print(f"Running test: {item}") 61 | t.__getattribute__(item)() 62 | -------------------------------------------------------------------------------- /tests/unit/phase/PhaseTest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from numpy.testing import assert_allclose 3 | 4 | import openpnm as op 5 | 6 | 7 | class PhaseTest: 8 | def setup_class(self): 9 | self.net = op.network.Cubic(shape=[10, 10, 10]) 10 | 11 | def teardown_class(self): 12 | mgr = op.Workspace() 13 | mgr.clear() 14 | 15 | def test_instantiate_without_network_fails(self): 16 | with pytest.raises(TypeError): 17 | op.phase.Phase() 18 | 19 | def test_getitem_interpolation(self): 20 | pn = op.network.Demo() 21 | air = op.phase.Air(network=pn) 22 | assert_allclose(air['pore.density'][0], 1.17982341) 23 | assert_allclose(air['throat.density'][0], 1.17982341) 24 | air['throat.test'] = 0.5 25 | assert_allclose(air['pore.test'][0], 0.5) 26 | with pytest.raises(KeyError): 27 | air['throat.density1'] 28 | with pytest.raises(KeyError): 29 | air['pore.density1'] 30 | 31 | def test_getitem_from_network(self): 32 | pn = op.network.Demo() 33 | air = op.phase.Air(network=pn) 34 | assert air['pore.left'].sum() == 3 35 | 36 | def test_getitem_domains(self): 37 | pn = op.network.Demo() 38 | air = op.phase.Air(network=pn) 39 | assert len(air['pore.density@left']) == 3 40 | assert_allclose(air['pore.density@left'][0], 1.17982341) 41 | air['pore.test'] = False 42 | air['pore.test'][[0, 1]] = True 43 | assert len(air['pore.density@test']) == 2 44 | with pytest.raises(KeyError): 45 | air['pore.density@blah'] 46 | 47 | def test_getitem_param(self): 48 | pn = op.network.Demo() 49 | air = op.phase.Air(network=pn) 50 | assert len(air['param.formula']) == 2 51 | 52 | def test_getitem_interpolation_disabled(self): 53 | pn = op.network.Demo() 54 | air = op.phase.Air(network=pn) 55 | air.settings['auto_interpolate'] = False 56 | with pytest.raises(KeyError): 57 | air['throat.density'] 58 | 59 | 60 | if __name__ == '__main__': 61 | 62 | t = PhaseTest() 63 | self = t 64 | t.setup_class() 65 | for item in t.__dir__(): 66 | if item.startswith('test'): 67 | print(f"Running test: {item}") 68 | t.__getattribute__(item)() 69 | -------------------------------------------------------------------------------- /tests/unit/phase/WaterTest.py: -------------------------------------------------------------------------------- 1 | import numpy.testing as nptest 2 | 3 | import openpnm as op 4 | 5 | 6 | class WaterTest: 7 | def setup_class(self): 8 | self.net = op.network.Cubic(shape=[5, 5, 5]) 9 | self.phase = op.phase.Water(network=self.net) 10 | 11 | def test_generic_prop_with_temperature(self): 12 | T1 = 298.0 13 | T2 = 333.0 14 | vals = { 15 | 'pore.viscosity': 16 | { 17 | T1: 0.000893190947459112, 18 | T2: 0.00046734380929754017, 19 | }, 20 | 'pore.density': 21 | { 22 | T1: 996.9522269370573, 23 | T2: 983.3169657125407, 24 | }, 25 | 'pore.thermal_conductivity': 26 | { 27 | T1: 0.6104761069075901, 28 | T2: 0.6500614873117142, 29 | }, 30 | 'pore.molar_density': 31 | { 32 | T1: 55339.25794864455, 33 | T2: 54582.3859364129, 34 | }, 35 | 'pore.surface_tension': 36 | { 37 | T1: 0.07199532948823899, 38 | T2: 0.06626423376953479, 39 | }, 40 | 'pore.vapor_pressure': 41 | { 42 | T1: 3150.408314323942, 43 | T2: 19812.906299267815, 44 | }, 45 | } 46 | for prop in vals.keys(): 47 | print(f'Testing {prop}') 48 | self.phase['pore.temperature'] = T1 49 | self.phase.regenerate_models() 50 | val = self.phase[prop][0] 51 | nptest.assert_allclose(val, vals[prop][T1], rtol=1e-10) 52 | self.phase['pore.temperature'] = T2 53 | self.phase.regenerate_models() 54 | val = self.phase[prop][0] 55 | nptest.assert_allclose(val, vals[prop][T2], rtol=1e-10) 56 | 57 | 58 | if __name__ == '__main__': 59 | 60 | t = WaterTest() 61 | self = t 62 | t.setup_class() 63 | for item in t.__dir__(): 64 | if item.startswith('test'): 65 | print(f"Running test: {item}") 66 | t.__getattribute__(item)() 67 | -------------------------------------------------------------------------------- /tests/unit/skgraph/test_qupc.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from openpnm._skgraph.queries import ( 3 | qupc_initialize, 4 | qupc_reduce, 5 | qupc_update, 6 | qupc_compress, 7 | ) 8 | 9 | 10 | class SKGRQUPCTest: 11 | def setup_class(self): 12 | pass 13 | 14 | def test_basic(self): 15 | a = qupc_initialize(10) 16 | qupc_update(a, 4, 2) 17 | qupc_update(a, 7, 4) 18 | qupc_update(a, 9, 6) 19 | qupc_update(a, 6, 2) 20 | qupc_update(a, 5, 9) 21 | assert np.all(a == [0, 1, 2, 3, 2, 6, 2, 2, 8, 6]) 22 | qupc_reduce(a) 23 | assert np.all(a == [0, 1, 2, 3, 2, 2, 2, 2, 8, 2]) 24 | qupc_update(a, 9, 9) 25 | qupc_update(a, 0, 1) 26 | qupc_update(a, 8, 0) 27 | assert np.all(a == [1, 1, 2, 3, 2, 2, 2, 2, 1, 9]) 28 | qupc_reduce(a) 29 | qupc_compress(a) 30 | assert np.all(a == [0, 0, 1, 2, 1, 1, 1, 1, 0, 3]) 31 | 32 | 33 | if __name__ == '__main__': 34 | t = SKGRQUPCTest() 35 | t.setup_class() 36 | self = t 37 | for item in t.__dir__(): 38 | if item.startswith('test'): 39 | print(f'Running test: {item}') 40 | t.__getattribute__(item)() 41 | -------------------------------------------------------------------------------- /tests/unit/topotools/.pylint.d/TestGeneratorTools1.stats: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PMEAL/OpenPNM/7fd91d9d5accafd8ac58f343b4ce16bc143640fe/tests/unit/topotools/.pylint.d/TestGeneratorTools1.stats --------------------------------------------------------------------------------