From e4740f9e1280f42c6dffd51d221641643029b04f Mon Sep 17 00:00:00 2001 From: Adam Cheng <52572642+adamchengtkc@users.noreply.github.com> Date: Fri, 27 Oct 2023 12:34:21 +0200 Subject: [PATCH 01/21] DOC: guidelines --- .github/workflows/python-test.yml | 4 +- CONTRIBUTING.md | 134 ++++++++++++++++++++++++++++++ LICENSE => LICENSE.md | 0 SECURITY.md | 17 ++++ 4 files changed, 153 insertions(+), 2 deletions(-) create mode 100644 CONTRIBUTING.md rename LICENSE => LICENSE.md (100%) create mode 100644 SECURITY.md diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 01d2688..a1a6ef6 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -33,9 +33,9 @@ jobs: - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names - poetry run flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude=examples + poetry run flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude=docs # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - poetry run flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --exclude=examples + poetry run flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --exclude=docs - name: Test with pytest run: | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..0750539 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,134 @@ + +# Contributing + + +Contributions are welcome, and they are greatly appreciated! Every +little bit helps, and credit will always be given. + +You can contribute in many ways: + +## Types of Contributions + + +### Report Bugs + + +Report bugs at https://github.com/equinor/temperer/issues. + +If you are reporting a bug, please include: + +* Your operating system name and version. +* temperer version +* Detailed steps to reproduce the bug. + +### Fix Bugs + + +Look through the Git issues for bugs. Anything tagged with "bug" +and "help wanted" is open to whoever wants to implement it. + +### Implement Features + +Look through the Git issues for features. Anything tagged with "enhancement" +and "help wanted" is open to whoever wants to implement it. + +### Write Documentation + +We could always use more documentation, whether as part of the +official docs, in docstrings, or even on the web in blog posts, +articles, and such. + +### Submit Feedback + +The best way to send feedback is to file an issue +at https://github.com/equinor/temperer/issues. + +If you are proposing a feature: + +* Explain in detail how it would work. +* Keep the scope as narrow as possible, to make it easier to implement. + +## Get Started! + +Ready to contribute? Here's how to set up ``temperer`` for local development. + +1. Fork the ``temperer`` repo on Github equinor to your personal user +2. Clone your fork locally +3. Start the development container. [Info](https://containers.dev/) +4. Create a branch for local development: +5. Now you can make your changes locally. + +6. When you're done making changes, check that your changes pass flake8 and the tests +``` +poetry run flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude=docs +poetry run pytest --cov-report=term-missing --cov=temperer tests/ | tee pytest-coverage.txt +``` + +7. Commit your changes and push your branch to GitHub: +8. Submit a pull request through the Github website. + + + +Writing commit messages +----------------------- + +Commit messages should be clear and follow a few basic rules. Example: + +``` + ENH: add functionality X to numpy.. +``` +The first line of the commit message starts with a capitalized acronym +(options listed below) indicating what type of commit this is. Then a blank +line, then more text if needed. Lines shouldn't be longer than 72 +characters. If the commit is related to a ticket, indicate that with +``"See #3456", "Cf. #3344, "See ticket 3456", "Closes #3456"`` or similar. + +Read Chris Beams hints on commit messages . + +Describing the motivation for a change, the nature of a bug for bug fixes or +some details on what an enhancement does are also good to include in a commit message. +Messages should be understandable without looking at the code changes. +A commit message like FIX: fix another one is an example of what not to do; +the reader has to go look for context elsewhere. + +Standard acronyms to start the commit message with are: + +``` + API: an (incompatible) API change (will be rare) + PERF: performance or bench-marking + BLD: change related to building xtgeo + BUG: bug fix + FIX: fixes wrt to technical issues, e.g. wrong requirements.txt + DEP: deprecate something, or remove a deprecated object + DOC: documentation, addition, updates + ENH: enhancement, new functionality + CLN: code cleanup, maintenance commit (refactoring, typos, PEP, etc.) + REV: revert an earlier commit + TST: addition or modification of tests + REL: related to releasing xtgeo +``` + + +## Type hints + +PEP 484 + +## Docstrings + +Numpy docstring. + +https://numpydoc.readthedocs.io/en/latest/format.html + +## Style guidelines + + + + +## Pull Request Guidelines + +Before you submit a pull request, check that it meets these guidelines: + +1. The pull request should include tests. +2. If the pull request adds functionality, the docstrings and/or docs should be updated. + + diff --git a/LICENSE b/LICENSE.md similarity index 100% rename from LICENSE rename to LICENSE.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..6863544 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,17 @@ +If you discover a security vulnerability in this project, please follow these steps to responsibly disclose it: + +1. **Do not** create a public GitHub issue for the vulnerability. + +2. Follow our guideline for Responsible Disclosure Policy at https://www.equinor.com/about-us/csirt to report the issue + + +The following information will help us triage your report more quickly: + +- Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) +- Full paths of source file(s) related to the manifestation of the issue +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- Step-by-step instructions to reproduce the issue +- Proof-of-concept or exploit code (if possible) +- Impact of the issue, including how an attacker might exploit the issue +- We prefer all communications to be in English. \ No newline at end of file From 8408ba6f6cf04d2ced2c5bfd5e2159459b8dcee2 Mon Sep 17 00:00:00 2001 From: Adam Cheng <52572642+adamchengtkc@users.noreply.github.com> Date: Fri, 27 Oct 2023 13:18:04 +0200 Subject: [PATCH 02/21] DOC: remove grid example --- docs/example.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/example.rst b/docs/example.rst index bbeb8c1..cbeb72f 100644 --- a/docs/example.rst +++ b/docs/example.rst @@ -7,4 +7,3 @@ Example :caption: Contents: notebooks/Build_within_Python - notebooks/Build_with_surface_grids From b8eff9149e05a187eeb79e1f77a7d2c0dee3973b Mon Sep 17 00:00:00 2001 From: Adam Cheng <52572642+adamchengtkc@users.noreply.github.com> Date: Fri, 27 Oct 2023 13:22:07 +0200 Subject: [PATCH 03/21] DOC: remove grid example --- ...d_with_surface_grids.ipynb => Build_with_surface_grids.ipynb_} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/notebooks/{Build_with_surface_grids.ipynb => Build_with_surface_grids.ipynb_} (100%) diff --git a/docs/notebooks/Build_with_surface_grids.ipynb b/docs/notebooks/Build_with_surface_grids.ipynb_ similarity index 100% rename from docs/notebooks/Build_with_surface_grids.ipynb rename to docs/notebooks/Build_with_surface_grids.ipynb_ From 67ebe0068f930a8bd704ab27d531c802795d8279 Mon Sep 17 00:00:00 2001 From: Adam Cheng <52572642+adamchengtkc@users.noreply.github.com> Date: Fri, 27 Oct 2023 11:30:19 +0000 Subject: [PATCH 04/21] DOC: change name --- CONTRIBUTING.md | 21 ++++++--------------- README.md | 30 ++++++++++++++---------------- 2 files changed, 20 insertions(+), 31 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0750539..11a1ecf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,5 @@ - # Contributing - Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given. @@ -9,10 +7,8 @@ You can contribute in many ways: ## Types of Contributions - ### Report Bugs - Report bugs at https://github.com/equinor/temperer/issues. If you are reporting a bug, please include: @@ -23,7 +19,6 @@ If you are reporting a bug, please include: ### Fix Bugs - Look through the Git issues for bugs. Anything tagged with "bug" and "help wanted" is open to whoever wants to implement it. @@ -56,7 +51,7 @@ Ready to contribute? Here's how to set up ``temperer`` for local development. 2. Clone your fork locally 3. Start the development container. [Info](https://containers.dev/) 4. Create a branch for local development: -5. Now you can make your changes locally. +5. Make your changes locally. 6. When you're done making changes, check that your changes pass flake8 and the tests ``` @@ -64,8 +59,8 @@ poetry run flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics -- poetry run pytest --cov-report=term-missing --cov=temperer tests/ | tee pytest-coverage.txt ``` -7. Commit your changes and push your branch to GitHub: -8. Submit a pull request through the Github website. +7. Commit your changes and push your branch +8. Submit a pull request. @@ -108,21 +103,17 @@ Standard acronyms to start the commit message with are: REL: related to releasing xtgeo ``` - ## Type hints -PEP 484 +[PEP 484](https://peps.python.org/pep-0484/) ## Docstrings -Numpy docstring. - -https://numpydoc.readthedocs.io/en/latest/format.html +[Numpy](https://numpydoc.readthedocs.io/en/latest/format.html) ## Style guidelines - - +[PEP 8](https://peps.python.org/pep-0008/) ## Pull Request Guidelines diff --git a/README.md b/README.md index 8aa1a0c..9d5beaa 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,30 @@ -# SubsHeat -## Forward modeling of thermal evolution in geological time +# Temperer +## Forward modeling of thermal evolution through geological time -![Build Status](https://github.com/equinor/SubsHeat/actions/workflows/python-test.yml/badge.svg?branch=main) -![Build Status](https://github.com/equinor/SubsHeat/actions/workflows/docs.yml/badge.svg?branch=main) +![Build Status](https://github.com/equinor/temperer/actions/workflows/python-test.yml/badge.svg?branch=main) +![Build Status](https://github.com/equinor/temperer/actions/workflows/docs.yml/badge.svg?branch=main) -[Documentation](https://curly-adventure-5mpe5wj.pages.github.io/) +[Documentation](https://fuzzy-meme-o4w5534.pages.github.io/) -SubsHeat is a python package used for modeling thermal evolution based on McKenzie's type basin extension. It can be use for: +Temperer is a python package used for modeling thermal evolution based on McKenzie's type basin extension. It can be use for: - Finding beta factor - Calculating subsidence and thermal history - Basement heat flow through time ## Features - +- Multi-1D simulation +- Full 3D simulation with dolfinx - Build model from either: - Python objects - [XTGeo](https://github.com/equinor/xtgeo/) supported surface formats - - [PetroMod](https://www.software.slb.com/products/petromod) models using [petromodder](https://github.com/equinor/petromodder) - Multi-rift phase support -- Numba accelerated -- Dask support +- Ensemble models with ERT https://github.com/equinor/ert + +## Install + +`pip install temperer` -## SubsHeat3D 3D simulations in SubsHeat are under active development. Currently, yhey are based on a uniform reactangular grid of 1D nodes (defined in a NodeGrid data structure). The sediment inputs, horizons are present day, are provided as .gri files, which are read into a SedimentStack class. ### Pre-requisite: grid of 1D node simulation @@ -59,12 +61,8 @@ subsheat ``` ## Development +[Contributing guidelines](CONTRIBUTING.md) -Easiest is to use codespace: -https://docs.github.com/en/codespaces/getting-started/quickstart - -Or use our devcontainer: -https://code.visualstudio.com/docs/remote/containers ## Run tests From a9ee20baaa75f0db1d746b939dc6e3b2f8ca65b8 Mon Sep 17 00:00:00 2001 From: Adam Cheng <52572642+adamchengtkc@users.noreply.github.com> Date: Fri, 27 Oct 2023 12:06:19 +0000 Subject: [PATCH 05/21] DOC: installation --- CONTRIBUTING.md | 4 +--- README.md | 48 ++++++++---------------------------------------- docs/theory.md | 20 +++++++++++++++++++- 3 files changed, 28 insertions(+), 44 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 11a1ecf..a08ac2c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -63,9 +63,7 @@ poetry run pytest --cov-report=term-missing --cov=temperer tests/ | tee pytest-c 8. Submit a pull request. - -Writing commit messages ------------------------ +## Writing commit messages Commit messages should be clear and follow a few basic rules. Example: diff --git a/README.md b/README.md index 9d5beaa..0800678 100644 --- a/README.md +++ b/README.md @@ -21,52 +21,20 @@ Temperer is a python package used for modeling thermal evolution based on McKenz - Multi-rift phase support - Ensemble models with ERT https://github.com/equinor/ert -## Install - -`pip install temperer` - -3D simulations in SubsHeat are under active development. Currently, yhey are based on a uniform reactangular grid of 1D nodes (defined in a NodeGrid data structure). The sediment inputs, horizons are present day, are provided as .gri files, which are read into a SedimentStack class. - -### Pre-requisite: grid of 1D node simulation -The complete 1D SubsHeat simulation is run for some of the 1D nodes. For other 1D nodes the subsidence, beta factor, and crustal thickness are interpolated. The 1D simulations can be run using the script [parallel-1Dsed.py](subsheat3D/parallel-1Dsed.py). Results for each node are pickled to a separate file (this is to be improved!). - -### 3D heat equation simulation using dolfinx -The 3D simulation performs a series of heat equation solves, regularly updating the mesh positions from the 1D nodes. The equations are solved using the PETSc solver from the dolfinx package (part of the FeNiCs project). The compute mesh is built by defining hexahedra for every rectangle of 1D nodes and for every layer (i.e. each sediment, the crust, the lithosphere, and the aesthenosphere), which are then subdivided into tetrahedra. - -The dolfinx model building and solving is managed by the class [UniformNodeGridFixedSizeMeshModel](subsheat3D/fixed_mesh_model.py). The use of this class is demonstrated in [subsheat3D_mapA_example.py](tests/subsheat3D_mapA_example.py). Note that the NodeGrid class definition in this script should match the definition used in [parallel-1Dsed.py](subsheat3D/parallel-1Dsed.py) to compute the 1D solutions. This script writes the results (mesh positions and function values) at every 1M years in xdmf format for visualization in ParaView. - -### RESQML output -The test script [subsheat3D_mapA_example.py](tests/subsheat3D_mapA_example.py) further demonstrates writing the unstructured grid (with properties) in RESQML format, as a pair of .epc and .h5 files. The RESQML I/O functions are in a separate file, [resqpy_helpers.py](subsheat3D/resqpy_helpers.py), and require a modified version of the resqpy library. To visualise RESQML data in ParaView, a 3rd-party plug-in can be installed, see [fespp](https://github.com/F2I-Consulting/fespp). - -### SubsHeat3D dependencies -The dolfinx package is Linux-only(?) and has to be compiled from source or installed using apt-get. The resqpy dependency can be installed with pip, but, for now, some writing of properties on unstructured grids requires a change in resqpy that is not yet merged. The other dependencies xtgeo and meshio can be installed using pip (requirements file is to be added). - - ## Installation -SubsHeat support Python >=3.7, <3.10. - -Install from source by cloning this repo +Until it is available on pypi, you will need to clone the repo ``` -git clone https://github.com/equinor/SubsHeat.git -pip install -e . +git clone git@github.com:equinor/temperer.git +pip install . ``` -or install from Azure private feed. Contact us for access - +For a specific release ``` -pip install --extra-index-url \ -https://x:@pkgs.dev.azure.com/Equinor/DIPVP/_packaging/ET_PSM/pypi/simple \ -subsheat +git clone git@github.com:equinor/temperer.git --branch +pip install . ``` -## Development -[Contributing guidelines](CONTRIBUTING.md) - - -## Run tests - -`poetry run pytest --cov-report=term-missing --cov=subsheat tests/ | tee pytest-coverage.txt` - -## License +For full 3D simulation, dolfinx is required. +See https://docs.fenicsproject.org/dolfinx/main/python/installation.html for installation instructions. diff --git a/docs/theory.md b/docs/theory.md index a7d9167..7727584 100644 --- a/docs/theory.md +++ b/docs/theory.md @@ -1 +1,19 @@ -# Background theory \ No newline at end of file +# Background theory + + +3D simulations in temperer are under active development. Currently, yhey are based on a uniform reactangular grid of 1D nodes (defined in a NodeGrid data structure). The sediment inputs, horizons are present day, are provided as .gri files, which are read into a SedimentStack class. + +### Pre-requisite: grid of 1D node simulation +The complete 1D temperer simulation is run for some of the 1D nodes. For other 1D nodes the subsidence, beta factor, and crustal thickness are interpolated. The 1D simulations can be run using the script [parallel-1Dsed.py](temperer3D/parallel-1Dsed.py). Results for each node are pickled to a separate file (this is to be improved!). + +### 3D heat equation simulation using dolfinx +The 3D simulation performs a series of heat equation solves, regularly updating the mesh positions from the 1D nodes. The equations are solved using the PETSc solver from the dolfinx package (part of the FeNiCs project). The compute mesh is built by defining hexahedra for every rectangle of 1D nodes and for every layer (i.e. each sediment, the crust, the lithosphere, and the aesthenosphere), which are then subdivided into tetrahedra. + +The dolfinx model building and solving is managed by the class [UniformNodeGridFixedSizeMeshModel](temperer3D/fixed_mesh_model.py). The use of this class is demonstrated in [temperer3D_mapA_example.py](tests/temperer3D_mapA_example.py). Note that the NodeGrid class definition in this script should match the definition used in [parallel-1Dsed.py](temperer3D/parallel-1Dsed.py) to compute the 1D solutions. This script writes the results (mesh positions and function values) at every 1M years in xdmf format for visualization in ParaView. + +### RESQML output +The test script [temperer3D_mapA_example.py](tests/temperer3D_mapA_example.py) further demonstrates writing the unstructured grid (with properties) in RESQML format, as a pair of .epc and .h5 files. The RESQML I/O functions are in a separate file, [resqpy_helpers.py](temperer3D/resqpy_helpers.py), and require a modified version of the resqpy library. To visualise RESQML data in ParaView, a 3rd-party plug-in can be installed, see [fespp](https://github.com/F2I-Consulting/fespp). + +### 3D dependencies +The dolfinx package is Linux-only(?) and has to be compiled from source or installed using apt-get. The resqpy dependency can be installed with pip, but, for now, some writing of properties on unstructured grids requires a change in resqpy that is not yet merged. The other dependencies xtgeo and meshio can be installed using pip (requirements file is to be added). + From 4926532d9c60646c40c9f32313220868d93b03f5 Mon Sep 17 00:00:00 2001 From: Adam Cheng <52572642+adamchengtkc@users.noreply.github.com> Date: Fri, 27 Oct 2023 12:48:27 +0000 Subject: [PATCH 06/21] TST: remove old test file --- tests/benchmark/hypothetical/0.gri | Bin 41712 -> 0 bytes tests/benchmark/hypothetical/30.gri | Bin 41712 -> 0 bytes tests/benchmark/hypothetical/50.gri | Bin 41712 -> 0 bytes tests/benchmark/hypothetical/60.gri | Bin 41712 -> 0 bytes 4 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tests/benchmark/hypothetical/0.gri delete mode 100644 tests/benchmark/hypothetical/30.gri delete mode 100644 tests/benchmark/hypothetical/50.gri delete mode 100644 tests/benchmark/hypothetical/60.gri diff --git a/tests/benchmark/hypothetical/0.gri b/tests/benchmark/hypothetical/0.gri deleted file mode 100644 index f0a92a1466f28374f60585a24a8817567db392e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41712 zcmeIx&1u6>5QWh(q<|FQg-Zz`q?2@%qEb@`*iQm&vhwO8E=TYX`7y)zeP?EF_xs;< zX1>;$&+FUv+TZU#Guv6uZJpP3_WxP$rj(O}e>;dE9W*&~bVPZn#M|*D#M8 zZW=mH@4yW=>E;^dal=hR$LSro;U?W&!#r-dY3Mk;12^2Hn`@ZI4L1!Pr+46nn{;yx z^SI%rq2u%p+;Ed_u3;WG+%$BY-hmr#(#D)#|<|P9jABT zhMRPA4fD9+rlI5X4%~2)ZmwY-H{3LIoZf*OZqm&)%;ScehK|!aaKlZyxrTY%aMRFn sdIxT}NjKLpj~i|pI!^Dv4L9lL8s>4sO+&}&9k}5p-CV;wZqDuI0T$i=*Z=?k diff --git a/tests/benchmark/hypothetical/30.gri b/tests/benchmark/hypothetical/30.gri deleted file mode 100644 index 0cbc836c67f5c5a989432b40cc80c31416ea80f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41712 zcmeIxy=enM6ot_dxBwTRKz%q72D=mY;UZL&iW0~(3!6AnbXhKEVOabl9#8X~F~;Tj ze4NJ^Kl6<5^L4$p_vd|#b>?%K=Y5{-f989C*6-G5-{v{}JM+iwnSVdszCM13bFA-R z-HW?9)}8D|+|4oXW!;avaW}hD@7G)4=JoF?-Icp>H|gdY=5fPKL&xbIxZx(D)#|<|P9jABThMRPA4fD9+rlI5X4%~2)ZmwY-H{3LIoZf*OZqm&) z%;ScehK|!aaKlZyxrTY%aMRFndIxT}NjKLpj~i|pI!^Dv4L9lL8s>4sO+&}&9k}5p z-CV;wZn$aaIK2Zm+@zapn8yt_4IQU<;D(!Ya}D#j;ijSE^bXu`lWwkI9yi=Hbe!IS z8*b9gHO%9Nn}&|lJ8;8Iy19mV+;G#-ae4=CxJftHFpnE<8ahtzzzsL)<{IX4!%aiS z=^eP?Cf!`aJZ`vY=s3LtH{7I~YnaCkHw_)9ci@JbbaM^!xZ$Rurj(O}e>;dE9W*&~bVPZn#M|*D#NpW4rkSIaXN1 diff --git a/tests/benchmark/hypothetical/50.gri b/tests/benchmark/hypothetical/50.gri deleted file mode 100644 index 25fd9f6919a6160d6983c8055288454b3a481d67..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41712 zcmeI2!A-<45Jind0TjT2QV`N2(E@i0;7T1FDTX7W9L`8!udoQKD0s%>WE{VhtSlSb zJNfT_JJAwCxcd6~ybK{c#1?KZuhZdpym<>DwfMS(p z@%-8IhmDMlR%gvP*ubXJ1GQ_f*?5ir zwPPwZ8}42}tFvYtY*^DOJz%yb)z+c$zjjPzt*mK0-?+(5^0GDul5v%pNqP;J8`#M9 zX9JIkaw+k@rmxZU44IqO*?~=6HZ^%D8Dqp{*!?E!SE99~PoCH4IP991^^<77)Hly( zcpRQh(sD`jl5>VUruQ_kiEO6Ymav%inT|Dn51SJ<|LL2#{MXOmJZyw)3^f0f(}nq9 z1DjH>(C^nPa9j{LvFlz zvWI%e4Y}#Tr`~ELH{`~fCwr)e+>o0deCn;nX1Te44TqL(;dgd#8f)u;&46oK%gm&? z$&F5f)?8qt(JhsREhujQypT?TTn#P(Y_W-$(Yc}IE_8aya z^JktJpY82ljogqMawGQuxshwO*4o)6n*sKk#2+@~#&o|SH~QzN*3X`>fz8GqV83C% zVZV`kph<45*Migi%X)sN^L4?-sdlZOL180bYg#kO^TP%<*$W)Yy+CftwOZR(uz}6m lp4-FsksETe2OqC}4djO0tnIlyd>^?XH+%5$+SibB^9=%YAbS7+ diff --git a/tests/benchmark/hypothetical/60.gri b/tests/benchmark/hypothetical/60.gri deleted file mode 100644 index 565ce008463397eb0fb7156616ef3ad78ddf103b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41712 zcmeIx&1nNM6opY4Qa}pu!leX5(n|H z=i@xa_?~Bcoj=xVdtdKktTUg>Jn!>t|1;nFvwpWe`!>(%-t5W=vF>Cy;%<(4FYA8Xjl0>MdcWQRH?MzJ>8{+3yGb|KFpnE<8ahtzzzsL)<{IX4 z!%aiS=^eP?Cf!`aJZ`vY=s3LtH{7I~YnaCkHw_)9ci@JbbaM^!xZ$Rurj(O}e>;dE9W*&~bVPZn#M|*D#M8ZW=mH@4yW= z>E;^dal=hR$LSro;U?W&!#r-dY3Mk;12^2Hn`@ZI4L1!Pr+46nn{;yx^SI%rq2u%p z+;Ed_u3;WG+%$BY-hmr#(#D)#|<|P9jABThMRPA4fD9+ vrlI5X4%~2)ZmwY-H{3LIoZf*OZqm&)%;ScehK|!aaKlZyxrTY%9NWzwIJ`!0 From 25673e1f1b49760fc60d23de3b7697dc82f5db66 Mon Sep 17 00:00:00 2001 From: Adam Cheng <52572642+adamchengtkc@users.noreply.github.com> Date: Fri, 27 Oct 2023 12:50:17 +0000 Subject: [PATCH 07/21] TST: use git lfs for large file --- .devcontainer/devcontainer.json | 8 +++++++- .gitattributes | 1 + README.md | 4 ++++ docs/notebooks/data/0.gri | 3 +++ docs/notebooks/data/100.gri | 3 +++ docs/notebooks/data/163.gri | 3 +++ docs/notebooks/data/168.gri | 3 +++ docs/notebooks/data/170.gri | 3 +++ docs/notebooks/data/182.gri | 3 +++ docs/notebooks/data/66.gri | 3 +++ tests/benchmark/hypothetical/0.gri | 3 +++ tests/benchmark/hypothetical/30.gri | 3 +++ tests/benchmark/hypothetical/50.gri | 3 +++ tests/benchmark/hypothetical/60.gri | 3 +++ .../Top of 30MA - Temperature - at 0 Ma.gri | Bin 41712 -> 130 bytes .../Top of 50Ma- Temperature - at 0 Ma.gri | Bin 41712 -> 130 bytes .../30Ma - Temperature - 0 Ma.gri | Bin 41712 -> 130 bytes ...- Vertical Thermal Conductivity - 0 Ma.gri | Bin 40900 -> 130 bytes .../Base of model - Temperature - 0 Ma.gri | Bin 41712 -> 130 bytes ...- Vertical Thermal Conductivity - 0 Ma.gri | Bin 40900 -> 130 bytes .../Top of model - Temperature - 0 Ma.gri | Bin 41712 -> 130 bytes ...- Vertical Thermal Conductivity - 0 Ma.gri | Bin 40900 -> 130 bytes .../Base of Model - Temperature - 0 Ma.gri | Bin 41712 -> 130 bytes ...- Vertical Thermal Conductivity - 0 Ma.gri | Bin 40900 -> 130 bytes ...- Vertical Thermal Conductivity - 0 Ma.gri | Bin 40900 -> 130 bytes .../Top of 30 Ma - Temperature - 0 Ma.gri | Bin 41712 -> 130 bytes ...- Vertical Thermal Conductivity - 0 Ma.gri | Bin 40900 -> 130 bytes .../Top of model- Temperature - 0 Ma.gri | Bin 41712 -> 130 bytes ...Base of model - Conductivity - at 0 Ma.gri | Bin 40900 -> 130 bytes .../Base of model - Temperature - at 0 Ma.gri | Bin 41712 -> 130 bytes .../Top of 30MA - Conductivity - at 0 Ma.gri | Bin 40900 -> 130 bytes .../Top of 30MA - Temperature - at 0 Ma.gri | Bin 41712 -> 130 bytes .../Top of 50MA - Temperature - at 0 Ma.gri | Bin 41712 -> 130 bytes .../Top of 30MA - Temperature - at 0 Ma.gri | Bin 41712 -> 130 bytes .../Top of 50MA - Temperature - at 0 Ma.gri | Bin 41712 -> 130 bytes 35 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 .gitattributes create mode 100644 docs/notebooks/data/0.gri create mode 100644 docs/notebooks/data/100.gri create mode 100644 docs/notebooks/data/163.gri create mode 100644 docs/notebooks/data/168.gri create mode 100644 docs/notebooks/data/170.gri create mode 100644 docs/notebooks/data/182.gri create mode 100644 docs/notebooks/data/66.gri create mode 100644 tests/benchmark/hypothetical/0.gri create mode 100644 tests/benchmark/hypothetical/30.gri create mode 100644 tests/benchmark/hypothetical/50.gri create mode 100644 tests/benchmark/hypothetical/60.gri diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 6d395aa..6e4cfad 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -18,7 +18,13 @@ "vscode": { "extensions":["ms-python.python", "njpwerner.autodocstring"] } - } + }, + "features": { + "ghcr.io/devcontainers/features/git-lfs:1": { + "autoPull": true, + "version": "latest" + } + } // 👇 Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. // "remoteUser": "root" diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..5e22f49 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.gri filter=lfs diff=lfs merge=lfs -text diff --git a/README.md b/README.md index 0800678..0246fc6 100644 --- a/README.md +++ b/README.md @@ -38,3 +38,7 @@ pip install . For full 3D simulation, dolfinx is required. See https://docs.fenicsproject.org/dolfinx/main/python/installation.html for installation instructions. + +## Why the name? + +We are very bad at naming things. \ No newline at end of file diff --git a/docs/notebooks/data/0.gri b/docs/notebooks/data/0.gri new file mode 100644 index 0000000..1af556c --- /dev/null +++ b/docs/notebooks/data/0.gri @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:67506fbe1db74315561e3e251690b747ee208adf606f20339feefd98a2dbe7ce +size 222628 diff --git a/docs/notebooks/data/100.gri b/docs/notebooks/data/100.gri new file mode 100644 index 0000000..2920602 --- /dev/null +++ b/docs/notebooks/data/100.gri @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:977a6abc8956060dfe410163383070af9d0b3265b0e378ecffa452369fbe692a +size 222628 diff --git a/docs/notebooks/data/163.gri b/docs/notebooks/data/163.gri new file mode 100644 index 0000000..f870ea8 --- /dev/null +++ b/docs/notebooks/data/163.gri @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fe26b4139eb3ddfd40671f21325951ac55370d1bf17ea43a655c567c04c370d4 +size 222628 diff --git a/docs/notebooks/data/168.gri b/docs/notebooks/data/168.gri new file mode 100644 index 0000000..4decfb9 --- /dev/null +++ b/docs/notebooks/data/168.gri @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3bb9768864f99c86df978818b605fe6f1165781557392458a50b35b91c0654da +size 222628 diff --git a/docs/notebooks/data/170.gri b/docs/notebooks/data/170.gri new file mode 100644 index 0000000..113790d --- /dev/null +++ b/docs/notebooks/data/170.gri @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b509f2b4ad888816510be1402abf1de777037ae70dbb68eacc4a666aaebeb3c +size 222628 diff --git a/docs/notebooks/data/182.gri b/docs/notebooks/data/182.gri new file mode 100644 index 0000000..0925c2a --- /dev/null +++ b/docs/notebooks/data/182.gri @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ffafa4ddcbfec39760f314cf11cd7841826fefe7585147346ae24bb8c90adea5 +size 222628 diff --git a/docs/notebooks/data/66.gri b/docs/notebooks/data/66.gri new file mode 100644 index 0000000..72a48c1 --- /dev/null +++ b/docs/notebooks/data/66.gri @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:743aff905b9abba575555cf70a1b3c2a73af7c13abfa0b710a5149ca88e6e29b +size 222628 diff --git a/tests/benchmark/hypothetical/0.gri b/tests/benchmark/hypothetical/0.gri new file mode 100644 index 0000000..750be18 --- /dev/null +++ b/tests/benchmark/hypothetical/0.gri @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5c888313a31734576a15377af42e55319a8186f830f76db6f37467807ff23d42 +size 41712 diff --git a/tests/benchmark/hypothetical/30.gri b/tests/benchmark/hypothetical/30.gri new file mode 100644 index 0000000..96e20c9 --- /dev/null +++ b/tests/benchmark/hypothetical/30.gri @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a702f2e85175c3a0766d036561530764e844efecc7174fceca3bcb0a96d73682 +size 41712 diff --git a/tests/benchmark/hypothetical/50.gri b/tests/benchmark/hypothetical/50.gri new file mode 100644 index 0000000..e314e71 --- /dev/null +++ b/tests/benchmark/hypothetical/50.gri @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:48f6d60589d41955521f440af9d1e1e8f9ad5d11da9c9df61fd397a1de429a65 +size 41712 diff --git a/tests/benchmark/hypothetical/60.gri b/tests/benchmark/hypothetical/60.gri new file mode 100644 index 0000000..d9956b5 --- /dev/null +++ b/tests/benchmark/hypothetical/60.gri @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e698bd77c7d9661afdc934257733220e91dcd4bd3e42c470e297da699f41f950 +size 41712 diff --git a/tests/benchmark/results-hypothetical/all shale/Top of 30MA - Temperature - at 0 Ma.gri b/tests/benchmark/results-hypothetical/all shale/Top of 30MA - Temperature - at 0 Ma.gri index 75a6cb596588352720141106ce9102ed8511fb69..a912c18794834d8993f1b49e113f810a7fa5b7b7 100644 GIT binary patch literal 130 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 uX^L87patl!PtefxYXOLsU?%Krw diff --git a/tests/benchmark/results-hypothetical/layercake_all_sand/30Ma - Temperature - 0 Ma.gri b/tests/benchmark/results-hypothetical/layercake_all_sand/30Ma - Temperature - 0 Ma.gri index 44242a50c5f6ec0ed80868f5b8b41ff771e3730d..c17f7ea6d1ff37e0358bb5fa2384be99f984dea7 100644 GIT binary patch literal 130 zcmWN?NfN>!6a~ONr{Dqx9^o_OHjF}*N=h&bPp`e{tGs6)FW%NV=OM(suiK-}?SK2k zEsdv`XG!7$8$I*cf+BMyqoGuwC>445kl|oU(cbT{5`+kosaCJ8mIgLCnQ3<^DG>Qg MZ1!*A8WxKE0HP2j;Q#;t literal 41712 zcmeIzF-`+P5Jb_j1RFTw01kW%_gn%Dh){TySS^>tj&%X;p=v)EaNldo?Wx*!a7ue)_oYSz`9O{1jv%qPOKdb7`6*jqS^bI!EKIkxy({gizO)eXK zgG~qjSMR`Sxw*k6myN!`rrOmV=5bnXZm`K^qi?Y3;Q#6!I4w6f*yOU&H`r9Wy2Ct9 z%gqfoxoq?eHXZz5y#uG^<_4QwHu?seYFBrd$7#8_!6uiDzQLx0|EqW4wA|calgmcm zU{met4)ZuIH#gYive7r#bnt)m4xE;o8*Fmf=o@URUEN_Gr{(4bn_M>f2AdB4uik;v za&v=CE*pJ=O|`2#%;U7&++dT-M&Dr5!T;4ea9VC|u*qekZ?LI$b%%MJmYW-Fa@pt` zY&!VAdIwI+%?&oWZ1fE_)voR^kJEBy2WutGf>EQqB9XKsFH`wH|(KpysySl?XPRq>=Ho0u{4K^M8U%dmT<>m&PTsHa! ln`&2gn8#_kxxprv&F{Y1pU&6i=ghBt|G&>;`50q&`34#O%%=bV diff --git a/tests/benchmark/results-hypothetical/layercake_all_sand/30Ma - Vertical Thermal Conductivity - 0 Ma.gri b/tests/benchmark/results-hypothetical/layercake_all_sand/30Ma - Vertical Thermal Conductivity - 0 Ma.gri index b215272c3e71491914b9351e3ae14e13cf6d61d0..550fba914d4f065733649e6fde9b9a787b8d64ad 100644 GIT binary patch literal 130 zcmWN@NfN>!5CFhCuiyiQeamlP7=$X7ltXgx_2$%l;g`nz@V?eTyAaQ@9=l4=kS#ZIXYASktCj>Oc1c2&;j?wvIRR)PkH5p6uS%a@Op-UlK MM&pOv$|NQG1NclQpa1{> literal 40900 zcmeIxF=|3V6ougtl0Ib$3l|_+gQN&qNEVcJWle&a5nd-)o_F923=cJOFV6psF;2JJ z^*F}(nP+?*FY9rCe*V3l*O@O*^SsS-|DXBX{?>czyD!@t51#Y$!|&ze{oC2kZ?Nvf zz3k^cHXH6`e|NI(#=W?g&8X+|7I=C78znk&FYYDLUF%Fern-^XZ-L=l-g_qWL q6MOT*OQO5hnY{4Q+HPWRUU*4#*E*9IURv8t?9B@=iSAlwE-(MY=ISy4 diff --git a/tests/benchmark/results-hypothetical/layercake_all_sand/Base of model - Temperature - 0 Ma.gri b/tests/benchmark/results-hypothetical/layercake_all_sand/Base of model - Temperature - 0 Ma.gri index 5c9ad4ac584c9269fb521316cc5f7ac1762057b9..b3910f9c342733bf88689ca320a9efe1102bc780 100644 GIT binary patch literal 130 zcmWNGTMoh?5CH!<1sAY%cX{?U$U{g}8W;QF=_Q$Dl9{V~xR1ANbDn%C_2~2Vth_C^ z?|n<-rT8pKT_8r!l3PS$5Ae7snc^OdHo<4WgjA|Q6TQ_g5mc&`yz_duImQ61S_hWa N41)WwNDU59`~j()CHDXT literal 41712 zcmeIyF=|3V6ougtvVbhW!leX@)NUd>(Z<>ZrOcW_z|0dKq)1_PcpN@qcwiE)*ZIGU zF%I|p&wh;YG0%A4zb%i)^Y}H!a^~wW&+9yo-z+G z4lbwVUfj)=?qvNe?q<9HE8LB{S)F=5-vT$!e^=?Q+>N_QH`g$a8*UmpPVc}CH|gdY z=5fPKL&xbIxZx(D)#|<|P9jABThMRPA4fD9+rlI5X4%~2) zZmwY-H{3LIoZf*OZqm&)%;ScehK|!aaKlZyxrTY%aMRFndIxT}NjKLpj~i|pI!^Dv z4L9lL8s>4sO+&}&9k}5p-CV;wZn$aaIK2Zm+@zapn8yt_4IQU<;D(!Ya}D#j;ijSE z^bXu`lWwkI9yi=Hbe!IS8*b9gHO%9Nn}&|lJ8;8Iy19mV+;G#-ae4=CxJftHFpnE< z8ahtzzzsL)<{IX4!%aiS=^eP?Cf!`aJZ`vY=s3LtH{7I~YnaCkHw_)9ci@JbbaM^! zxZ$Rurj(O}e>;d4JtpPRFaWo$uiM IJ;v_#3tw7WM*si- diff --git a/tests/benchmark/results-hypothetical/layercake_all_sand/Base of model - Vertical Thermal Conductivity - 0 Ma.gri b/tests/benchmark/results-hypothetical/layercake_all_sand/Base of model - Vertical Thermal Conductivity - 0 Ma.gri index b215272c3e71491914b9351e3ae14e13cf6d61d0..550fba914d4f065733649e6fde9b9a787b8d64ad 100644 GIT binary patch literal 130 zcmWN@NfN>!5CFhCuiyiQeamlP7=$X7ltXgx_2$%l;g`nz@V?eTyAaQ@9=l4=kS#ZIXYASktCj>Oc1c2&;j?wvIRR)PkH5p6uS%a@Op-UlK MM&pOv$|NQG1NclQpa1{> literal 40900 zcmeIxF=|3V6ougtl0Ib$3l|_+gQN&qNEVcJWle&a5nd-)o_F923=cJOFV6psF;2JJ z^*F}(nP+?*FY9rCe*V3l*O@O*^SsS-|DXBX{?>czyD!@t51#Y$!|&ze{oC2kZ?Nvf zz3k^cHXH6`e|NI(#=W?g&8X+|7I=C78znk&FYYDLUF%Fern-^XZ-L=l-g_qWL q6MOT*OQO5hnY{4Q+HPWRUU*4#*E*9IURv8t?9B@=iSAlwE-(MY=ISy4 diff --git a/tests/benchmark/results-hypothetical/layercake_all_sand/Top of model - Temperature - 0 Ma.gri b/tests/benchmark/results-hypothetical/layercake_all_sand/Top of model - Temperature - 0 Ma.gri index 9b8185647cae22e8f39e5636ff53045e77c8a4b7..6b0d5489c0f1fcbb17674f148c95503573b48b6b 100644 GIT binary patch literal 130 zcmWN?OA^8$3;@tQr{DsXCJ;Wo4U{0vsB}#2!qe;9yo=v5=1ceUJY+NGKF-^t$@0H{ z%CgU=k%M=0ftsUg=^4pyu3ARIQeuq7W&~(NKG>Y{T34zN8vqkkj3IF1(%0Ky?CBs0IIR&n4?9m`s?@BTGz+p z`?A*h*x&VjxgFy<-@ev5zWa0C-`D+p{?4BBIevEh-3;iMtFLJX=S_9G zXJazqE!nUOTW@4zwW-r=S>Zj|unS*)>NKCp+)FlAn>uZ7)Hk#7nULc;ja}HBva#CK zX|}BJo^05KFF$pf&t&c;8>>y7HaF^<+4xMzah=94Y);u&ZR#{zR(MY~?829yI?ZP? z_mYj(rcRq1_04R2CgiwIV;453Y^*kQnk_55CmVL*%TJx=GnsqI#%fci&5inIHa-(_ zT&J-Mn^QJcn>x*w72cB#yYS_wPV>y7X3Gli z$%b9{@>8ezOy*v)vD(yWbECeQjn9M}*Ja@90-^|8mLXPV+c42eM#%fci*|NfWvSAm#{M2balew2{tTuJp+^BD6<1-=0bsD>{ zIb~zDsncv(;XT=~3txWfG@r@bOEy-UI&E&$H?#4XkmEXyUD%wmvD(yWwyf};Y}kb_ zKXsbVWbP#!t4*CYH|m?&_)N%goyIO~PT5#(>NHzccuzL$!k3>q&1W+Al8x1-PMaI` z&1`%oy9sB%RD1kPa5Ktu{jyW%qern-^XZ-L=l-g_qWL6MOT*OQO5hnY{4Q+HPWRUU*4# z*E*9IURv8t?9B@=iSAlw^1@4NyNSJd;U&>s>r7sFX>B*LH!r*-x@(=u3oothCidoq zmqd51GkM{qwcW(tyzr9fu5~6aytKBP*qaw#65X}V(Z<>ZrOcW_z|0dKq)1_PcpN@qcwiE)*ZIGU zF%I|p&wh;YG0%A4zb%i)^Y}H!a^~wW&+9yo-z+G z4lbwVUfj)=?qvNe?q<9HE8LB{S)F=5-vT$!e^=?Q+>N_QH`g$a8*UmpPVc}CH|gdY z=5fPKL&xbIxZx(D)#|<|P9jABThMRPA4fD9+rlI5X4%~2) zZmwY-H{3LIoZf*OZqm&)%;ScehK|!aaKlZyxrTY%aMRFndIxT}NjKLpj~i|pI!^Dv z4L9lL8s>4sO+&}&9k}5p-CV;wZn$aaIK2Zm+@zapn8yt_4IQU<;D(!Ya}D#j;ijSE z^bXu`lWwkI9yi=Hbe!IS8*b9gHO%9Nn}&|lJ8;8Iy19mV+;G#-ae4=CxJftHFpnE< z8ahtzzzsL)<{IX4!%aiS=^eP?Cf!`aJZ`vY=s3LtH{7I~YnaCkHw_)9ci@JbbaM^! zxZ$Rurj(O}e>;d4JtpPRFaWo$uiM IJ;v_#3tw7WM*si- diff --git a/tests/benchmark/results-hypothetical/layercake_all_sand_isotropic/Base of mode - Vertical Thermal Conductivity - 0 Ma.gri b/tests/benchmark/results-hypothetical/layercake_all_sand_isotropic/Base of mode - Vertical Thermal Conductivity - 0 Ma.gri index b215272c3e71491914b9351e3ae14e13cf6d61d0..550fba914d4f065733649e6fde9b9a787b8d64ad 100644 GIT binary patch literal 130 zcmWN@NfN>!5CFhCuiyiQeamlP7=$X7ltXgx_2$%l;g`nz@V?eTyAaQ@9=l4=kS#ZIXYASktCj>Oc1c2&;j?wvIRR)PkH5p6uS%a@Op-UlK MM&pOv$|NQG1NclQpa1{> literal 40900 zcmeIxF=|3V6ougtl0Ib$3l|_+gQN&qNEVcJWle&a5nd-)o_F923=cJOFV6psF;2JJ z^*F}(nP+?*FY9rCe*V3l*O@O*^SsS-|DXBX{?>czyD!@t51#Y$!|&ze{oC2kZ?Nvf zz3k^cHXH6`e|NI(#=W?g&8X+|7I=C78znk&FYYDLUF%Fern-^XZ-L=l-g_qWL q6MOT*OQO5hnY{4Q+HPWRUU*4#*E*9IURv8t?9B@=iSAlwE-(MY=ISy4 diff --git a/tests/benchmark/results-hypothetical/layercake_all_sand_isotropic/Top of 30 - Vertical Thermal Conductivity - 0 Ma.gri b/tests/benchmark/results-hypothetical/layercake_all_sand_isotropic/Top of 30 - Vertical Thermal Conductivity - 0 Ma.gri index b215272c3e71491914b9351e3ae14e13cf6d61d0..550fba914d4f065733649e6fde9b9a787b8d64ad 100644 GIT binary patch literal 130 zcmWN@NfN>!5CFhCuiyiQeamlP7=$X7ltXgx_2$%l;g`nz@V?eTyAaQ@9=l4=kS#ZIXYASktCj>Oc1c2&;j?wvIRR)PkH5p6uS%a@Op-UlK MM&pOv$|NQG1NclQpa1{> literal 40900 zcmeIxF=|3V6ougtl0Ib$3l|_+gQN&qNEVcJWle&a5nd-)o_F923=cJOFV6psF;2JJ z^*F}(nP+?*FY9rCe*V3l*O@O*^SsS-|DXBX{?>czyD!@t51#Y$!|&ze{oC2kZ?Nvf zz3k^cHXH6`e|NI(#=W?g&8X+|7I=C78znk&FYYDLUF%Fern-^XZ-L=l-g_qWL q6MOT*OQO5hnY{4Q+HPWRUU*4#*E*9IURv8t?9B@=iSAlwE-(MY=ISy4 diff --git a/tests/benchmark/results-hypothetical/layercake_all_sand_isotropic/Top of 30 Ma - Temperature - 0 Ma.gri b/tests/benchmark/results-hypothetical/layercake_all_sand_isotropic/Top of 30 Ma - Temperature - 0 Ma.gri index 44242a50c5f6ec0ed80868f5b8b41ff771e3730d..c17f7ea6d1ff37e0358bb5fa2384be99f984dea7 100644 GIT binary patch literal 130 zcmWN?NfN>!6a~ONr{Dqx9^o_OHjF}*N=h&bPp`e{tGs6)FW%NV=OM(suiK-}?SK2k zEsdv`XG!7$8$I*cf+BMyqoGuwC>445kl|oU(cbT{5`+kosaCJ8mIgLCnQ3<^DG>Qg MZ1!*A8WxKE0HP2j;Q#;t literal 41712 zcmeIzF-`+P5Jb_j1RFTw01kW%_gn%Dh){TySS^>tj&%X;p=v)EaNldo?Wx*!a7ue)_oYSz`9O{1jv%qPOKdb7`6*jqS^bI!EKIkxy({gizO)eXK zgG~qjSMR`Sxw*k6myN!`rrOmV=5bnXZm`K^qi?Y3;Q#6!I4w6f*yOU&H`r9Wy2Ct9 z%gqfoxoq?eHXZz5y#uG^<_4QwHu?seYFBrd$7#8_!6uiDzQLx0|EqW4wA|calgmcm zU{met4)ZuIH#gYive7r#bnt)m4xE;o8*Fmf=o@URUEN_Gr{(4bn_M>f2AdB4uik;v za&v=CE*pJ=O|`2#%;U7&++dT-M&Dr5!T;4ea9VC|u*qekZ?LI$b%%MJmYW-Fa@pt` zY&!VAdIwI+%?&oWZ1fE_)voR^kJEBy2WutGf>EQqB9XKsFH`wH|(KpysySl?XPRq>=Ho0u{4K^M8U%dmT<>m&PTsHa! ln`&2gn8#_kxxprv&F{Y1pU&6i=ghBt|G&>;`50q&`34#O%%=bV diff --git a/tests/benchmark/results-hypothetical/layercake_all_sand_isotropic/Top of Model- Vertical Thermal Conductivity - 0 Ma.gri b/tests/benchmark/results-hypothetical/layercake_all_sand_isotropic/Top of Model- Vertical Thermal Conductivity - 0 Ma.gri index 76c13d9311b48704bc3aa6465b16a98c384d48b7..220a218f400ec0aa2d05e46c16e09f4e0bb2e738 100644 GIT binary patch literal 130 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 Wm^5NV$4>tT#KZM2wE8h;KlcArjDYR{ diff --git a/tests/benchmark/results-hypothetical/sand_with_salt/Base of model - Temperature - at 0 Ma.gri b/tests/benchmark/results-hypothetical/sand_with_salt/Base of model - Temperature - at 0 Ma.gri index ffb6e5e78e32e1fb361afaac0f5208f99ff7b758..f617fff94499edf39b19495d5d8bc27aea0ee216 100644 GIT binary patch literal 130 zcmWN{K@!3s3;@78uiyg~0whR(1C$6eYC8sd@b&hzmp$7@#{SlQ&O=sX9%J5~EX!s8 zmZk15BS)*|1htP%OV18z4IaTGQ((%qF!(!Ipy=(EDiflsVZH95%*Ktmo)b NvrftQH60*=Q-8$mCZ+%Y literal 41712 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 NMZx1+rGp(n`~czaCtCmj literal 40900 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 BS=|5t diff --git a/tests/benchmark/results-hypothetical/sand_with_salt/Top of 50MA - Temperature - at 0 Ma.gri b/tests/benchmark/results-hypothetical/sand_with_salt/Top of 50MA - Temperature - at 0 Ma.gri index 132fb14b7004a37812427863dae5d6379202b44d..822e8d611d4aa4e1df665c5df6a2d98bc5bb5ceb 100644 GIT binary patch literal 130 zcmWN?OA^8$3;@u5Pr(H&pF$|T4Ix38QRx`$!qe;9yo=w`$4j+2PdT){_j!9%S^u{W zu4O#6oLux}m(fW{%Pzp-_CN$F1Cqh$8OeKJjdR)V#0+&S84M$&N;N>VxIr$E3WkW; N;v)Ua%G+q&@&gfvD186` literal 41712 zcmZs@3z(F1AOAlR7O6oP>|yp$M#4x~os73+w>`{#-gTZQVI+)@l`sg4Fh~Z)c$`+k z@Pw7r=m|>>BVipL2FXeoA&c;T)&71@&vpHOUDx~ieDCjl&&+*)-rvvhdv89UFZlQ0 zn@W8?-|4_Np)|5tY?12n`8I=pLEr~lu0j6af1Bqvf42Yso%nx9&Tlj4{Q3>A|6izO z+%UC_&8cNfQZ1t+Y8mBI%gB^k!dbNp&#NU=R7;himVJ?Xq|`Dftd<=j^8Nz9fc4<> zqP+L>@>XW$at5}`{=QB2 zTXES7)3R?wo?R=uu|)R4U{G4th8|hny|R{d%X+w7)-8-@hGd=9ChPbTS&eB~<2kNP z$T}o1>)^1ga7xyJTo;bZI*jvU+htAYk`?Wibz+CCDYddLLS7w^mEzp|URjT~$$Fk^ zUSV!kT-L`0Szm!4GO~UP%G#8Zy-h~;cE}xrvUlj+%*+3Sn@`~ePR78=sGM4kOu~&H zZs1_J0XGS?pvRVdIUbx>%Rr8|NAG`vpTW1_^Dx}_<-Ly1m*wR>#oPlqxbewrkH~8? zlEdV&i?XF^hD*FJ=4XBetbDJm58>rY@EzRz3O9eii!Uj=KeEIpdwZ;|j~i^|AozG4xe-i*7dROW zC!->8<5SC_aB*-}E!tkzzsTI=9iac zj`i=|5s`OYQr^WWxZ#|amp36H?@$o*%Nu~+e>3EM0UvK9 zQF*!L%x#mC^HoGn7qSC;Y44SD7QEDV%h{iCNr&vO;o%i{c`z;e>bUIFYGse-xT;I` zwym<(cF208Q`VD==c2bO+GL&0Tx++i<3i}2@D9?iJ9UDhd_o5H!aPFdH*`QCu62fJlG&3IX_tT(yl1NitHtSiX+Ifm`zWc>v% z{c`XU#D3CSxf#1yEk}J1FRQ@Aa04f!;pE7iT56MU0|(f1%K--5B;bbQy|ZfBo#O#T zxIyomz)xTu_$(`LbxPiA=zOUGH_ZJrB5zJo-nBV-)ARDq;GE}|cWhSP2vA|j+qo#W zHzoITbiXnv_Zgqudkb=}$;zFYkZXtK9$A#T4}AOuFP|e{Mm`*nb4`z&(>vuH3s2P{ zIorU)XLYh)gdf(feF?`cA=!rqWbcW-e(u6%YGo}(XPJtGO;EC&oa7~!cjAI-vk#%w#-^IC^0c?Q~&$T7%KF*pH-gOMQY!=DB5 zXA!m3a9oyA%U+EA1-0yyRLeFgdFa&p0jvd|1m(TsllO8#-eS0TJS*?MqP*L}@@6IE zU6_)08nD38;9yW1mRFLO`wJX=hU@}QfxE%wak;0r$!%>I^#2K*yx1k@ z9ypp=kaH4pbWBd5NA|{U+3&W?exg_Q>@@x?F8jDz*+bFY--^*$OFK7@Fn;VUWgaoa(HJB;2w8^rP_%rxf0T=hxVKeaJ1jqrph%;@(VV*yi zlXahuxB%eP8kfLMg0gl&_doZ_{5&mlRjtfdx@9gY$b1HF{*{yYoL^=qb4x)Ncpt23 zmHAUEb``+R(&P$oaCE0E7fz-yH-ovGL)d4Vtfw~f6O;8izx6)&oblI5Vh`gDK3N-) zKmWXynvNnNKa{L2M?bmR%X|lu=9nh`c`xc^kkt zpa9;3m)GHC8N4h?%6qsd@2;@C8x!&_%gQ?^FYhGK07ilRQt}3e<@xe*ze~t{-!GRq z<#vEu!F259M0hzmBzGu0m9)xPhrO)qB}eO#a|3+D3-~i+WlZ)T9kM@2%YGVOZtNt^ zFdo$-d-qyd>(R{$bTJP-Opg%rK^P27$t=Rb2Vt2nVk;eSnRj9rH-=;;;baE$ZJeK( z=a{+n4w-i#A3{Eh>_YyhN9Hf^v2(YqD& vdj`$XMjuj?OQ_R9GuJMuvva{rH|MH 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 Wu<68~C3mnHup76mzYg7QH~$Y0=;;Cg diff --git a/tests/benchmark/results-hypothetical/with salt/Top of 30MA - Temperature - at 0 Ma.gri b/tests/benchmark/results-hypothetical/with salt/Top of 30MA - Temperature - at 0 Ma.gri index 211126e08f8767adf72f967fa2f673e0a767ebdb..da273a7bc252f8189bb87d12a36dd8025054cd64 100644 GIT binary patch literal 130 zcmWN?%MrpL5CG6SRnUL|mgSdj0xZIeO2)+;tX|*cUG(NLU$U=t@~+fAf zR_0UjNkv^o%uyxx1DxM6+nf_Ls+h#J3wQtP!LMOV2Y2fS>1H- NsR|xnl4ybf@dMoyCiVaT literal 41712 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 z%0A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≠3LW?M`{06`8%E} z-awoE6LR^9)$-(34&Xe!1$#8~B+l1+>jgEOGej-T8`ScT2VwG^I`BOUGiv#rr`E;^ zYW*QG!Ut>A);U#ei`S{`j&8MW#m?SftolE2yj5-Y!5hcbGS`>#_PvL^fd{i!|dUb7N{_TaG>Uwjky58V^ zYp1$yhR@uiuKnuOxnYYsez93?qxPtEU-W*Ib)g05|J%`OtgP0E$4}I-BVOQ4&V5uj z$l$k!iJjLNs;=?zX9;`JGUN_=?DfcyFGM-|Q>Fma+i7m5Ur^2l{Y~~AQU4Ey_%kQX zt(X-MXC=e{G5*R*ZKjKQYxmKmgu*xNW<0MQ9bq#@+Sg1irem6!@EqWo^fLv%8|MG^ z*-MnpKU@O`KJ(r-c~J(}cAqwp`2*XtNjE?47V@ zfS6!?r)ur3GCMiH=jv|Gr{MROo?OKI%VhEc$geq*zWeu%+K3<8|4cveTXeZ_i@Nrw z?w6v$j!uoO*s8JPd&o_jps}ZI(b&VfHFf}>VXV~UuTxj`8gil5sole#zDItIwyF77 z2Wrgh85*@at9oOch7^Zr(20{}Hn^&tiOsxSWG@ym|B_f0tMF%`!@e5&n(1ir24%tz zD_t&F?2s;Rw`xz05j<8C3>CRQ_jTFV@Vyr_&?$2iykxFuHWBjDjct#`tgz$e00 zrdmE@owJ!7fluMJ_{KBnyT&pWI)U--v~tcw->Jc;V!Ks5M{A;*8$$N}SgJbEQ{{eW zGXk5Li8fDoDjQm4-UA3t-T^-bCz|AKxXQj;Q1AH(x1h%*=rGs9AA#ezK0KkX&tNYR zdKhQ}x0kqp-GD;@wwE0bCc9{3rt{SF(Y@oDxv&Qo`NU=L58zAT)h6x0MVkUPQbZGQ zJ=Z^W(|m**<34TnMwbID&Ng$Da}xX;{H{d~7ky8huurYP`JN`fZEC7wS3y;rd0c(! z9@V^DuYqgO=e*sV8FspcUdwMWU&~pGM{~y5gFE?srl}hJbT?yu#xhL_YtzKWgFH3$ zzz;`UiQ=fJyY=a|B?53E`ADqJdS$-Nf&x0=j zi^2B|w1-#2Yt2%hf-VJYq=+719oILy>Dt74>tEVrcX;?*p5wESJjRT2r^9!}%J)wg zk7iVM5WekUgEJK(RZKBdc`SR9=5JQ@1Jp~+Rt?;A4`=MH;wf(9o4# z=+h?T|Nq=m_Sp=35iD{|(dT@RygKyhu$2Erfjm!BWq-$ZeqCVxliz*69)0dwqv}_v zpXwPJ*nN-8UvT!$o^cvHnDgdG_R~<-L5FfS*|0UChUXIvzuMC9U7qT@z=A~e>%oIu zKbnEThCuZzz;Olk5o}<%gN*oa3is%|-B1aIt~Sz*oS5 zi7qa>6tI;ddVrg`zQs+~TYr-DX|pMLxTvA49e$fGNd@{Ew{K10 z9FsXsumD^HC~x+X0yYC*3IEVe`~4dt?8QJA?r(u_;lALP@^$;PnQqZ$c&=p$Z79Ib z<8&YUNg2vRxyo(j`M$C$LuC)4jayVjjj4*A=<}qjs!gV4Q14O z{#qmD_+!F8KgxAns9X*8@FDf^n^Ha4^v{%kF6Ey_`6pBUk}b4@F)nrj{;%3h=WphM zg`fwl0IP~$_5bjXeEPQtn=zzQw*$whOv7HIve_WNX!nd`8KT|if^{QpG;e(5+-h)Q>6VslKPM^CPNLw-Y zqHgDd=V|xlmg=UW+2y|Kep*!BuWZ#lYjF0ssk(c)zRD$^Hz5A}CdkGY>2ROThQRWhVX% zz6QQFqW?GOXKehLj~3kD3e!%r8x8I=eRflyC_9?x8W*BZOuxnT0~Y!7l;cQGx%Fsc zpp9p9t^xLQQL*>zyz&CtjCWP>iK)t)B78pWc&DxED`?lXrfN2$(MU8}AF#hI!#>?u z1Ah!&!KU`{WezEDhGHbs2Ay0F^=0mlp4$=)yphjO_Eh_EMzxn%s`*E(n%VUEF9xie z@qaF+O>8TwvXN(4o2dNHfy&Q8lQ*%Mb7JK`36;MJZbgrGDaXwrInNpLYCXzDJv>W2 z+(A98jp!G6jrNe8Oqt~{{&!P*8BtVrIQYM6b1r`~A6y8Qf|X#ki$8+{_@A2l$&WO_cZOVAyNJ(v3mD> z7h=YwR_}RD6{ncQ*64Fdf#2H!n|xJ|q;22ntEMti&Bf^SIyzCOwTs|;itOD5pP<9u zpueGk`x&aG&T6-VyG_+z;Brnxk@e*QYh|9QKSz@l#L4)nsZduYN5|MHa2t|I{n;L&Hm_fJp3d0w}sw( zi`xEZPzCBDVsTrw2im+IspgrYY63np*;Mu4T;|Bor3+oQ2ArEhJQf4`g^H&`e*eZH zc0?D&?6LP$c6Nq!XG{6@JgXArKH=FPfG-FbUt6p_Q?@r9a-AvP4H?R5rE^|Wis=|~ zEIcMscBHTDFnCyrU34n)8axti^3f)u9UEw4r?J440@{GHz)Ulp+guDU1HOwk#jnOR z@C{L43{diAo4CInzQbnkjsf>+GvL#r2J9}PkE`rJFs}5x0k-9_t`Rd9piIj%ta&7? zBg875K~YxbaLxs~91y5{jmwz>MOE{Oe`>NW z=~C~YyQrETu)-(59LzR&9Skq1`p-pG-_PeMXZ3(YrSmVVCZfq7169t7u~T$elu`L# z6Z$@fvu;C`6*BBk@s$6gqx?#qb#%b~W}bZ`JU!to7>E48ko>@e{Tf^c7Jet7otwnK zl>0Ea1GaqZ#8!4FJha3Gv@uiL83{K<#Lx!X*l6RUO%b~Rr-HM)U@ zD)@&c{>(rd8@ut-zW;XaZ;#M~`#WvskG`hO2c`ym2tF_1+dvD?dqzk+;o#Ri{93Hs zEXo#Tls7Hq*HZSsB+R>07jJ~@*`xp2hBnl9#c$B2%U9)H=yDLYGZ%iA`YofLPls=S zpN2m+RnxzqnkrChr@rriNY%S+&Klw~*YlaF1#~H@>etxIbdTI~bXku+Lladz6sY2G z{&$QnuBEbFvC1y<@Mk>ZQ;G8PW1cl+|9`;VC-}6Oy_yMrAVZnJJt6HlroZ+WD^ljk zMaCa;dS;m`O+VzuBSkV|S?Ry?!cN*@-=O6Q7~aJO^zuX%5;o{gj)wg*mC6 z!~yFVBTyIjQV&-aw*{Xzf$JXPrOo(!UT=dt_n-~0 zAHI&UCv|l^b#;17yypR%K8)|W5MBXa46inc1q^Jph)+w<#=&kvVt@=b!+UqS%uf{9 N>%;rEJEd6Q{{h5wqk8}V diff --git a/tests/benchmark/results-hypothetical/with salt/Top of 50MA - Temperature - at 0 Ma.gri b/tests/benchmark/results-hypothetical/with salt/Top of 50MA - Temperature - at 0 Ma.gri index 79dbe5367aa59f58c9e3cc189414158f1af82015..5a6e9c682e51f7df45e2fc5d6e64235d73babf97 100644 GIT binary patch literal 130 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 Date: Fri, 27 Oct 2023 13:03:45 +0000 Subject: [PATCH 08/21] DOC: rename --- .github/workflows/python-test.yml | 2 +- CONTRIBUTING.md | 12 ++++++------ README.md | 16 ++++++---------- docs/apireference.rst | 14 +++++++------- docs/conf.py | 4 ++-- docs/index.rst | 2 +- docs/notebooks/Build_with_surface_grids.ipynb_ | 6 +++--- docs/notebooks/Build_within_Python.ipynb | 10 +++++----- docs/theory.md | 8 ++++---- pyproject.toml | 2 +- subsheat3D/SedimentStack.py | 8 ++++---- subsheat3D/fixed_mesh_model.py | 2 +- subsheat3D/parallel-1Dsed.py | 8 ++++---- subsheat3D/sed_only_mesh_model.py | 2 +- tests/benchmark/parallel-1Dsed-hypothetical.py | 8 ++++---- tests/test_forward_model.py | 2 +- tests/test_grid.py | 2 +- tests/test_integration.py | 14 +++++++------- tests/ttest_from_grid.py | 6 +++--- {temperer => warmth}/__init__.py | 0 {temperer => warmth}/build.py | 4 ++-- {temperer => warmth}/data.py | 0 {temperer => warmth}/data/haq87.json | 0 {temperer => warmth}/forward_modelling.py | 0 {temperer => warmth}/logging.py | 0 {temperer => warmth}/model.py | 0 {temperer => warmth}/parameters.py | 2 +- {temperer => warmth}/postprocessing.py | 0 {temperer => warmth}/simulator.py | 2 +- {temperer => warmth}/utils.py | 0 30 files changed, 66 insertions(+), 70 deletions(-) rename {temperer => warmth}/__init__.py (100%) rename {temperer => warmth}/build.py (96%) rename {temperer => warmth}/data.py (100%) rename {temperer => warmth}/data/haq87.json (100%) rename {temperer => warmth}/forward_modelling.py (100%) rename {temperer => warmth}/logging.py (100%) rename {temperer => warmth}/model.py (100%) rename {temperer => warmth}/parameters.py (95%) rename {temperer => warmth}/postprocessing.py (100%) rename {temperer => warmth}/simulator.py (99%) rename {temperer => warmth}/utils.py (100%) diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index a1a6ef6..0d50f8d 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -39,7 +39,7 @@ jobs: - name: Test with pytest run: | - poetry run pytest --cov-report=term-missing --cov=temperer tests/ | tee pytest-coverage.txt + poetry run pytest --cov-report=term-missing --cov=warmth tests/ | tee pytest-coverage.txt - name: Comment coverage if: ${{ github.event_name == 'pull_request' && github.event.action == 'opened' }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a08ac2c..04b38ef 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,12 +9,12 @@ You can contribute in many ways: ### Report Bugs -Report bugs at https://github.com/equinor/temperer/issues. +Report bugs at https://github.com/equinor/warmth/issues. If you are reporting a bug, please include: * Your operating system name and version. -* temperer version +* warmth version * Detailed steps to reproduce the bug. ### Fix Bugs @@ -36,7 +36,7 @@ articles, and such. ### Submit Feedback The best way to send feedback is to file an issue -at https://github.com/equinor/temperer/issues. +at https://github.com/equinor/warmth/issues. If you are proposing a feature: @@ -45,9 +45,9 @@ If you are proposing a feature: ## Get Started! -Ready to contribute? Here's how to set up ``temperer`` for local development. +Ready to contribute? Here's how to set up ``warmth`` for local development. -1. Fork the ``temperer`` repo on Github equinor to your personal user +1. Fork the ``warmth`` repo on Github equinor to your personal user 2. Clone your fork locally 3. Start the development container. [Info](https://containers.dev/) 4. Create a branch for local development: @@ -56,7 +56,7 @@ Ready to contribute? Here's how to set up ``temperer`` for local development. 6. When you're done making changes, check that your changes pass flake8 and the tests ``` poetry run flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude=docs -poetry run pytest --cov-report=term-missing --cov=temperer tests/ | tee pytest-coverage.txt +poetry run pytest --cov-report=term-missing --cov=warmth tests/ | tee pytest-coverage.txt ``` 7. Commit your changes and push your branch diff --git a/README.md b/README.md index 0246fc6..5da8537 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ -# Temperer +# Warmth ## Forward modeling of thermal evolution through geological time -![Build Status](https://github.com/equinor/temperer/actions/workflows/python-test.yml/badge.svg?branch=main) -![Build Status](https://github.com/equinor/temperer/actions/workflows/docs.yml/badge.svg?branch=main) +![Build Status](https://github.com/equinor/warmth/actions/workflows/python-test.yml/badge.svg?branch=main) +![Build Status](https://github.com/equinor/warmth/actions/workflows/docs.yml/badge.svg?branch=main) [Documentation](https://fuzzy-meme-o4w5534.pages.github.io/) -Temperer is a python package used for modeling thermal evolution based on McKenzie's type basin extension. It can be use for: +warmth is a python package used for modeling thermal evolution based on McKenzie's type basin extension. It can be use for: - Finding beta factor - Calculating subsidence and thermal history @@ -26,19 +26,15 @@ Temperer is a python package used for modeling thermal evolution based on McKenz Until it is available on pypi, you will need to clone the repo ``` -git clone git@github.com:equinor/temperer.git +git clone git@github.com:equinor/warmth.git pip install . ``` For a specific release ``` -git clone git@github.com:equinor/temperer.git --branch +git clone git@github.com:equinor/warmth.git --branch pip install . ``` For full 3D simulation, dolfinx is required. See https://docs.fenicsproject.org/dolfinx/main/python/installation.html for installation instructions. - -## Why the name? - -We are very bad at naming things. \ No newline at end of file diff --git a/docs/apireference.rst b/docs/apireference.rst index 43093d8..7c3d927 100644 --- a/docs/apireference.rst +++ b/docs/apireference.rst @@ -5,32 +5,32 @@ API Reference Model --------------- -.. automodule:: temperer.model +.. automodule:: warmth.model :members: Model builder --------------- -.. automodule:: temperer.build +.. automodule:: warmth.build :members: -.. autoclass:: temperer.build.Builder +.. autoclass:: warmth.build.Builder :members: Parameters --------------- -.. automodule:: temperer.parameters +.. automodule:: warmth.parameters :members: Forward model --------------- -.. automodule:: temperer.forward_modelling +.. automodule:: warmth.forward_modelling :members: Simulator --------------- -.. automodule:: temperer.simulator +.. automodule:: warmth.simulator :members: Results --------------- -.. automodule:: temperer.postprocessing +.. automodule:: warmth.postprocessing :members: \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index 4ae29a8..d7d20b8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -8,7 +8,7 @@ import os import sys sys.path.insert(0, os.path.abspath('..')) -project = 'temperer' +project = 'warmth' copyright = '2023, Equinor ASA' author = 'Adam Cheng' @@ -22,7 +22,7 @@ html_sourcelink_suffix = '' nbsphinx_prolog = """ -`Download notebook `_ +`Download notebook `_ ---- """ diff --git a/docs/index.rst b/docs/index.rst index 439a541..f1ac86e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,4 +1,4 @@ -Welcome to Temperer's documentation! +Welcome to warmth's documentation! ==================================== diff --git a/docs/notebooks/Build_with_surface_grids.ipynb_ b/docs/notebooks/Build_with_surface_grids.ipynb_ index f03a806..95271b7 100644 --- a/docs/notebooks/Build_with_surface_grids.ipynb_ +++ b/docs/notebooks/Build_with_surface_grids.ipynb_ @@ -22,7 +22,7 @@ "\n", "sys.path.append(\"../\")\n", "import numpy as np\n", - "import temperer" + "import warmth" ] }, { @@ -68,7 +68,7 @@ "metadata": {}, "outputs": [], "source": [ - "model = temperer.Model()" + "model = warmth.Model()" ] }, { @@ -140,7 +140,7 @@ "metadata": {}, "outputs": [], "source": [ - "from temperer.data import haq87\n", + "from warmth.data import haq87\n", "model.builder.set_eustatic_sea_level(haq87)" ] }, diff --git a/docs/notebooks/Build_within_Python.ipynb b/docs/notebooks/Build_within_Python.ipynb index e4a5382..cd9ccf1 100644 --- a/docs/notebooks/Build_within_Python.ipynb +++ b/docs/notebooks/Build_within_Python.ipynb @@ -18,8 +18,8 @@ "import sys\n", "sys.path.append(\"../\")\n", "import numpy as np\n", - "import temperer\n", - "from temperer.forward_modelling import Forward_model" + "import warmth\n", + "from warmth.forward_modelling import Forward_model" ] }, { @@ -28,7 +28,7 @@ "metadata": {}, "outputs": [], "source": [ - "model = temperer.Model()" + "model = warmth.Model()" ] }, { @@ -66,7 +66,7 @@ "metadata": {}, "outputs": [], "source": [ - "node =temperer.single_node()\n", + "node =warmth.single_node()\n", "node.sediments_inputs = node_template\n", "node.sediments" ] @@ -108,7 +108,7 @@ "metadata": {}, "outputs": [], "source": [ - "from temperer.data import haq87\n", + "from warmth.data import haq87\n", "model.builder.set_eustatic_sea_level(haq87)" ] }, diff --git a/docs/theory.md b/docs/theory.md index 7727584..40300a5 100644 --- a/docs/theory.md +++ b/docs/theory.md @@ -1,18 +1,18 @@ # Background theory -3D simulations in temperer are under active development. Currently, yhey are based on a uniform reactangular grid of 1D nodes (defined in a NodeGrid data structure). The sediment inputs, horizons are present day, are provided as .gri files, which are read into a SedimentStack class. +3D simulations in warmth are under active development. Currently, yhey are based on a uniform reactangular grid of 1D nodes (defined in a NodeGrid data structure). The sediment inputs, horizons are present day, are provided as .gri files, which are read into a SedimentStack class. ### Pre-requisite: grid of 1D node simulation -The complete 1D temperer simulation is run for some of the 1D nodes. For other 1D nodes the subsidence, beta factor, and crustal thickness are interpolated. The 1D simulations can be run using the script [parallel-1Dsed.py](temperer3D/parallel-1Dsed.py). Results for each node are pickled to a separate file (this is to be improved!). +The complete 1D warmth simulation is run for some of the 1D nodes. For other 1D nodes the subsidence, beta factor, and crustal thickness are interpolated. The 1D simulations can be run using the script [parallel-1Dsed.py](warmth3D/parallel-1Dsed.py). Results for each node are pickled to a separate file (this is to be improved!). ### 3D heat equation simulation using dolfinx The 3D simulation performs a series of heat equation solves, regularly updating the mesh positions from the 1D nodes. The equations are solved using the PETSc solver from the dolfinx package (part of the FeNiCs project). The compute mesh is built by defining hexahedra for every rectangle of 1D nodes and for every layer (i.e. each sediment, the crust, the lithosphere, and the aesthenosphere), which are then subdivided into tetrahedra. -The dolfinx model building and solving is managed by the class [UniformNodeGridFixedSizeMeshModel](temperer3D/fixed_mesh_model.py). The use of this class is demonstrated in [temperer3D_mapA_example.py](tests/temperer3D_mapA_example.py). Note that the NodeGrid class definition in this script should match the definition used in [parallel-1Dsed.py](temperer3D/parallel-1Dsed.py) to compute the 1D solutions. This script writes the results (mesh positions and function values) at every 1M years in xdmf format for visualization in ParaView. +The dolfinx model building and solving is managed by the class [UniformNodeGridFixedSizeMeshModel](warmth3D/fixed_mesh_model.py). The use of this class is demonstrated in [warmth3D_mapA_example.py](tests/warmth3D_mapA_example.py). Note that the NodeGrid class definition in this script should match the definition used in [parallel-1Dsed.py](warmth3D/parallel-1Dsed.py) to compute the 1D solutions. This script writes the results (mesh positions and function values) at every 1M years in xdmf format for visualization in ParaView. ### RESQML output -The test script [temperer3D_mapA_example.py](tests/temperer3D_mapA_example.py) further demonstrates writing the unstructured grid (with properties) in RESQML format, as a pair of .epc and .h5 files. The RESQML I/O functions are in a separate file, [resqpy_helpers.py](temperer3D/resqpy_helpers.py), and require a modified version of the resqpy library. To visualise RESQML data in ParaView, a 3rd-party plug-in can be installed, see [fespp](https://github.com/F2I-Consulting/fespp). +The test script [warmth3D_mapA_example.py](tests/warmth3D_mapA_example.py) further demonstrates writing the unstructured grid (with properties) in RESQML format, as a pair of .epc and .h5 files. The RESQML I/O functions are in a separate file, [resqpy_helpers.py](warmth3D/resqpy_helpers.py), and require a modified version of the resqpy library. To visualise RESQML data in ParaView, a 3rd-party plug-in can be installed, see [fespp](https://github.com/F2I-Consulting/fespp). ### 3D dependencies The dolfinx package is Linux-only(?) and has to be compiled from source or installed using apt-get. The resqpy dependency can be installed with pip, but, for now, some writing of properties on unstructured grids requires a change in resqpy that is not yet merged. The other dependencies xtgeo and meshio can be installed using pip (requirements file is to be added). diff --git a/pyproject.toml b/pyproject.toml index 580a957..03858f4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [tool.poetry] -name = "temperer" +name = "warmth" version = "0.0.1" description = "" authors = ["Adam Cheng <52572642+adamchengtkc@users.noreply.github.com>"] diff --git a/subsheat3D/SedimentStack.py b/subsheat3D/SedimentStack.py index 929d1b8..6238761 100644 --- a/subsheat3D/SedimentStack.py +++ b/subsheat3D/SedimentStack.py @@ -1,7 +1,7 @@ import pandas as pd from xtgeo.surface import RegularSurface -import temperer -from temperer.data import haq87 +import warmth +from warmth.data import haq87 # | get_dataframe(self, ijcolumns=False, ij=False, order='C', activeonly=True, fill_value=nan) # | Return a Pandas dataframe object, with columns X_UTME, Y_UTMN, VALUES. @@ -56,14 +56,14 @@ def createHorizonsAtIJ(self, i, j): return horizons def create1DNodeAtIJ(self, i_ind, j_ind,): - model = temperer.Model() + model = warmth.Model() node_template = model.builder.single_node_sediments_inputs_template.copy() horizons = self.createHorizonsAtIJ(i_ind, j_ind) for j,i in enumerate(horizons): row = pd.DataFrame({'top': i[0], 'topage': int(i[1]), 'k_cond': i[4], 'rhp': i[5], 'phi': i[6], 'decay': i[7], 'solidus': i[8], 'liquidus': i[9]}, index=[j]) node_template = pd.concat( [node_template, row] ) - node = temperer.single_node() + node = warmth.single_node() node.sediments_inputs = node_template #node.bflux = False #node.adiab = 0.0 diff --git a/subsheat3D/fixed_mesh_model.py b/subsheat3D/fixed_mesh_model.py index 0ef5b3e..21e74bc 100644 --- a/subsheat3D/fixed_mesh_model.py +++ b/subsheat3D/fixed_mesh_model.py @@ -2,7 +2,7 @@ from mpi4py import MPI from scipy.interpolate import LinearNDInterpolator -from temperer.logging import logger +from warmth.logging import logger from subsheat3D.Helpers import NodeGrid, NodeParameters1D, top_crust,top_sed,thick_crust, thick_lith, top_lith, top_asth, top_sed_id, bottom_sed_id, thick_sed, volumeOfTet from subsheat3D.resqpy_helpers import write_tetra_grid_with_properties, write_hexa_grid_with_properties diff --git a/subsheat3D/parallel-1Dsed.py b/subsheat3D/parallel-1Dsed.py index d8ff7e9..fb7159a 100644 --- a/subsheat3D/parallel-1Dsed.py +++ b/subsheat3D/parallel-1Dsed.py @@ -3,8 +3,8 @@ from multiprocessing import Pool import itertools -import temperer -from temperer.data import haq87 +import warmth +from warmth.data import haq87 from subsheat3D.SedimentStack import SedimentStack from subsheat3D.Helpers import NodeGrid, getNodeParameters @@ -27,7 +27,7 @@ def run(self): invalid = np.any(np.isnan(np.array(node.sediments_inputs['top'].to_list()))) if (invalid): return False - model = temperer.Model() + model = warmth.Model() baseage = int(node.sediments["baseage"].iloc[-1]) model.parameters.time_start = baseage model.parameters.time_end = 0 @@ -44,7 +44,7 @@ def run(self): # compute only the sedimentation for this node, the crustal thickness and subsidence have to be interpolated # when the 3D simulation is set up! # - fw = temperer.forward_modelling.Forward_model(model.parameters, node) + fw = warmth.forward_modelling.Forward_model(model.parameters, node) fw._sedimentation() # # pad sed array with bottom (zero-sized?) sediments diff --git a/subsheat3D/sed_only_mesh_model.py b/subsheat3D/sed_only_mesh_model.py index 483c4ea..43bac4f 100644 --- a/subsheat3D/sed_only_mesh_model.py +++ b/subsheat3D/sed_only_mesh_model.py @@ -2,7 +2,7 @@ from mpi4py import MPI from scipy.interpolate import LinearNDInterpolator -from temperer.logging import logger +from warmth.logging import logger from subsheat3D.Helpers import NodeGrid, NodeParameters1D, top_crust, top_sed, thick_crust, thick_lith, top_lith, top_asth, top_sed_id, bottom_sed_id, thick_sed, volumeOfTet from subsheat3D.resqpy_helpers import write_tetra_grid_with_properties, write_hexa_grid_with_properties diff --git a/tests/benchmark/parallel-1Dsed-hypothetical.py b/tests/benchmark/parallel-1Dsed-hypothetical.py index b92cb6b..dd56fd2 100644 --- a/tests/benchmark/parallel-1Dsed-hypothetical.py +++ b/tests/benchmark/parallel-1Dsed-hypothetical.py @@ -3,8 +3,8 @@ from multiprocessing import Pool import itertools -import temperer -from temperer.data import haq87 +import warmth +from warmth.data import haq87 from subsheat3D.SedimentStack import SedimentStack from subsheat3D.Helpers import NodeGrid @@ -29,7 +29,7 @@ def run(self): if (invalid): print ("invalid", invalid, node.sediments_inputs['top'].to_list()) return False - model = temperer.Model() + model = warmth.Model() baseage = int(node.sediments["baseage"].iloc[-1]) model.parameters.time_start = baseage model.parameters.time_end = 0 @@ -47,7 +47,7 @@ def run(self): # compute only the sedimentation for this node, # if crustal thickness and subsidence are required in the 3D simulation, they will have to be interpolated # - fw = temperer.forward_modelling.Forward_model(model.parameters, node) + fw = warmth.forward_modelling.Forward_model(model.parameters, node) fw._sedimentation() # # pad sed array with bottom (zero-sized?) sediments diff --git a/tests/test_forward_model.py b/tests/test_forward_model.py index 9a45130..bcf29e3 100644 --- a/tests/test_forward_model.py +++ b/tests/test_forward_model.py @@ -1,5 +1,5 @@ import numpy as np -from temperer.forward_modelling import Forward_model +from warmth.forward_modelling import Forward_model def test_pad_multirift_results(): pad_func = Forward_model._equalise_array_shape diff --git a/tests/test_grid.py b/tests/test_grid.py index a6e04e3..f3188f9 100644 --- a/tests/test_grid.py +++ b/tests/test_grid.py @@ -1,3 +1,3 @@ -from temperer.build import Grid +from warmth.build import Grid import numpy as np diff --git a/tests/test_integration.py b/tests/test_integration.py index 41209c7..0fbb10b 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -1,9 +1,9 @@ import pandas as pd -import temperer -from temperer.data import haq87 +import warmth +from warmth.data import haq87 import numpy as np import pickle -from temperer.forward_modelling import Forward_model +from warmth.forward_modelling import Forward_model def sediments(template:pd.DataFrame)->pd.DataFrame: h1 = [152.0,0.0,1.500000,2.301755e-09,0.620000,0.500,2720.0,2448.0] @@ -36,9 +36,9 @@ def load_reference_data_multi_rift(): s = np.load(f) return t,d,s def test_integration_single_rift(): - model = temperer.Model() + model = warmth.Model() sed = sediments(model.builder.single_node_sediments_inputs_template) - node = temperer.single_node() + node = warmth.single_node() node.sediments_inputs = sed node.shf = 60e-3 node.qbase = 30e-3 @@ -59,9 +59,9 @@ def test_integration_single_rift(): def test_integration_multi_rift(): - model = temperer.Model() + model = warmth.Model() sed = sediments(model.builder.single_node_sediments_inputs_template) - node = temperer.single_node() + node = warmth.single_node() node.sediments_inputs = sed node.shf = 60e-3 node.qbase = 30e-3 diff --git a/tests/ttest_from_grid.py b/tests/ttest_from_grid.py index 2f6c959..bc82bab 100644 --- a/tests/ttest_from_grid.py +++ b/tests/ttest_from_grid.py @@ -4,12 +4,12 @@ from pathlib import Path import sys -import temperer +import warmth maps_dir = Path("./docs/data/mapA") -model = temperer.Model() +model = warmth.Model() inputs = model.builder.input_horizons_template @@ -28,7 +28,7 @@ model.builder.extract_nodes(4,maps_dir) -from temperer.data import haq87 +from warmth.data import haq87 model.builder.set_eustatic_sea_level(haq87) for i in model.builder.iter_node(): diff --git a/temperer/__init__.py b/warmth/__init__.py similarity index 100% rename from temperer/__init__.py rename to warmth/__init__.py diff --git a/temperer/build.py b/warmth/build.py similarity index 96% rename from temperer/build.py rename to warmth/build.py index 922c5e9..305cd06 100644 --- a/temperer/build.py +++ b/warmth/build.py @@ -12,7 +12,7 @@ import math import copy from dataclasses import dataclass -from temperer.utils import compressed_pickle_open, compressed_pickle_save +from warmth.utils import compressed_pickle_open, compressed_pickle_save from .logging import logger from .parameters import Parameters from .postprocessing import Results @@ -552,7 +552,7 @@ def extract_nodes( ) else: raise ValueError( - "Invalid input table. Check temperer.input_data_template") + "Invalid input table. Check warmth.input_data_template") # Check if age is sorted chk = self.input_horizons.apply(lambda x: x.is_monotonic_increasing) chk = chk["Age"] diff --git a/temperer/data.py b/warmth/data.py similarity index 100% rename from temperer/data.py rename to warmth/data.py diff --git a/temperer/data/haq87.json b/warmth/data/haq87.json similarity index 100% rename from temperer/data/haq87.json rename to warmth/data/haq87.json diff --git a/temperer/forward_modelling.py b/warmth/forward_modelling.py similarity index 100% rename from temperer/forward_modelling.py rename to warmth/forward_modelling.py diff --git a/temperer/logging.py b/warmth/logging.py similarity index 100% rename from temperer/logging.py rename to warmth/logging.py diff --git a/temperer/model.py b/warmth/model.py similarity index 100% rename from temperer/model.py rename to warmth/model.py diff --git a/temperer/parameters.py b/warmth/parameters.py similarity index 95% rename from temperer/parameters.py rename to warmth/parameters.py index fd4f124..8ff725c 100644 --- a/temperer/parameters.py +++ b/warmth/parameters.py @@ -1,7 +1,7 @@ from pathlib import Path import pandas as pd import pickle -from temperer.utils import compressed_pickle_open, compressed_pickle_save +from warmth.utils import compressed_pickle_open, compressed_pickle_save from .logging import logger diff --git a/temperer/postprocessing.py b/warmth/postprocessing.py similarity index 100% rename from temperer/postprocessing.py rename to warmth/postprocessing.py diff --git a/temperer/simulator.py b/warmth/simulator.py similarity index 99% rename from temperer/simulator.py rename to warmth/simulator.py index 2b4cff6..3b3f673 100644 --- a/temperer/simulator.py +++ b/warmth/simulator.py @@ -5,7 +5,7 @@ import time import numpy as np -from temperer.utils import load_pickle +from warmth.utils import load_pickle from .logging import logger from .forward_modelling import Forward_model from .build import Builder, single_node,load_node diff --git a/temperer/utils.py b/warmth/utils.py similarity index 100% rename from temperer/utils.py rename to warmth/utils.py From 3dc37c8b42c6fd575b59553a0d55c02e6206580a Mon Sep 17 00:00:00 2001 From: Jussi Aittoniemi Date: Fri, 27 Oct 2023 15:05:17 +0200 Subject: [PATCH 09/21] complete the test_from_grid integration test for subsheat3D; add node interpolation to builder.py --- subsheat3D/fixed_mesh_model.py | 212 ++++++++++++++++++++------- temperer/build.py | 27 ++++ tests/test_from_grid.py | 260 +++++++++++++++++++++++++++++++++ 3 files changed, 446 insertions(+), 53 deletions(-) create mode 100644 tests/test_from_grid.py diff --git a/subsheat3D/fixed_mesh_model.py b/subsheat3D/fixed_mesh_model.py index 0ef5b3e..2ac2474 100644 --- a/subsheat3D/fixed_mesh_model.py +++ b/subsheat3D/fixed_mesh_model.py @@ -29,7 +29,6 @@ class UniformNodeGridFixedSizeMeshModel: point_bottom_vertex_map = {} def __init__(self, nodeGrid, nodes, modelName="test", sedimentsOnly = False): self.node1D = nodes - self.nodeGrid = nodeGrid self.num_nodes = len(self.node1D) self.mesh = None @@ -60,7 +59,7 @@ def __init__(self, nodeGrid, nodes, modelName="test", sedimentsOnly = False): edge = [i*self.num_nodes_x + (self.num_nodes_x-1), (i+1)*self.num_nodes_x+ (self.num_nodes_x-1)] self.convexHullEdges.append(edge) - self.useBaseFlux = True + self.useBaseFlux = False self.baseFluxMagnitude = 0.06 self.mesh0_geometry_x = None @@ -71,7 +70,7 @@ def __init__(self, nodeGrid, nodes, modelName="test", sedimentsOnly = False): self.mean_porosity = None self.c_rho = None self.numberOfSediments = self.node1D[0].sed.shape[0] - self.globalSediments = self.node1D[0].sediments.copy() + # self.globalSediments = self.node1D[0].sediments.copy() self.parameters1D = self.node1D[0].parameters if hasattr(self.node1D[0], 'parameters') else NodeParameters1D() self.interpolators = {} @@ -97,8 +96,10 @@ def boundary(x): tet_to_keep = [] p_to_keep = set() lid_to_keep = [] + cell_id_to_keep = [] for i,t in enumerate(tet): ps = p0[i] + minY = np.amin( np.array( [p[1] for p in ps] ) ) midpoint = np.sum(ps,axis=0)*0.25 lid0 = self.findLayerID(self.tti, midpoint) # @@ -107,14 +108,23 @@ def boundary(x): if (lid0>=-1) and (lid0<100): tet_to_keep.append(t) lid_to_keep.append(lid0) + cell_id_to_keep.append(self.node_index[i]) + if abs(self.node_index[i].Y-minY)>1: + print("unusual Y coordinate:", minY, self.node1D[self.node_index[i]].Y, i, self.node_index[i], self.node1D[self.node_index[i]]) for ti in t: p_to_keep.add(ti) - poro0_per_cell = np.array( [ self.getSedimentPropForLayerID('phi', lid) for lid in lid_to_keep ] ) - decay_per_cell = np.array( [ self.getSedimentPropForLayerID('decay', lid) for lid in lid_to_keep ]) - density_per_cell = np.array( [ self.getSedimentPropForLayerID('solidus', lid) for lid in lid_to_keep ]) - cond_per_cell = np.array( [ self.getSedimentPropForLayerID('k_cond', lid) for lid in lid_to_keep ]) - rhp_per_cell = np.array( [ self.getSedimentPropForLayerID('rhp', lid) for lid in lid_to_keep ]) + # poro0_per_cell = np.array( [ self.getSedimentPropForLayerID('phi', lid) for lid in lid_to_keep ] ) + # decay_per_cell = np.array( [ self.getSedimentPropForLayerID('decay', lid) for lid in lid_to_keep ]) + # density_per_cell = np.array( [ self.getSedimentPropForLayerID('solidus', lid) for lid in lid_to_keep ]) + # cond_per_cell = np.array( [ self.getSedimentPropForLayerID('k_cond', lid) for lid in lid_to_keep ]) + # rhp_per_cell = np.array( [ self.getSedimentPropForLayerID('rhp', lid) for lid in lid_to_keep ]) + poro0_per_cell = np.array( [ self.getSedimentPropForLayerID('phi', lid,cid) for lid,cid in zip(lid_to_keep,cell_id_to_keep) ] ) + decay_per_cell = np.array( [ self.getSedimentPropForLayerID('decay', lid,cid) for lid,cid in zip(lid_to_keep,cell_id_to_keep) ]) + density_per_cell = np.array( [ self.getSedimentPropForLayerID('solidus', lid,cid) for lid,cid in zip(lid_to_keep,cell_id_to_keep) ]) + cond_per_cell = np.array( [ self.getSedimentPropForLayerID('k_cond', lid,cid) for lid,cid in zip(lid_to_keep,cell_id_to_keep) ]) + rhp_per_cell = np.array( [ self.getSedimentPropForLayerID('rhp', lid,cid) for lid,cid in zip(lid_to_keep,cell_id_to_keep) ]) + lid_per_cell = np.array(lid_to_keep) points_cached = [] @@ -148,12 +158,13 @@ def write_hexa_mesh_resqml( self, out_path): for ind,val in enumerate(self.mesh_reindex): x_original_order[val,:] = self.mesh.geometry.x[ind,:] reverse_reindex_order[val] = ind - hexaHedra, hex_data_layerID = self.buildHexahedra() + hexaHedra, hex_data_layerID, hex_data_nodeID = self.buildHexahedra() hexa_to_keep = [] p_to_keep = set() lid_to_keep = [] cond_per_cell = [] + cell_id_to_keep = [] for i,h in enumerate(hexaHedra): lid0 = hex_data_layerID[i] # @@ -162,17 +173,28 @@ def write_hexa_mesh_resqml( self, out_path): if (lid0>=-1) and (lid0<100): hexa_to_keep.append(h) lid_to_keep.append(lid0) + # cell_id_to_keep.append(self.node_index[i]) + cell_id_to_keep.append(hex_data_nodeID[i]) + minY = np.amin(np.array ( [x_original_order[hi,1] for hi in h] )) + if abs( self.node1D[hex_data_nodeID[i]].Y - minY)>1: + print("weird Y:", minY, self.node1D[hex_data_nodeID[i]].Y, abs( self.node1D[hex_data_nodeID[i]].Y - minY), i, hex_data_nodeID[i]) + breakpoint() + # if (minY>40000): + # pp = self.getSedimentPropForLayerID('phi', lid0, hex_data_nodeID[i]) + # if (pp<0.7) and lid0>=0: + # print("weird phi: ", pp, minY, self.node1D[hex_data_nodeID[i]].Y, abs( self.node1D[hex_data_nodeID[i]].Y - minY), i, hex_data_nodeID[i]) + # breakpoint() k_cond_mean = [] for hi in h: p_to_keep.add(hi) k_cond_mean.append(self.thermalCond.x.array[hi]) cond_per_cell.append( np.mean(np.array(k_cond_mean))) - poro0_per_cell = np.array( [ self.getSedimentPropForLayerID('phi', lid) for lid in lid_to_keep ] ) - decay_per_cell = np.array( [ self.getSedimentPropForLayerID('decay', lid) for lid in lid_to_keep ]) - density_per_cell = np.array( [ self.getSedimentPropForLayerID('solidus', lid) for lid in lid_to_keep ]) - cond_per_cell = np.array( [ self.getSedimentPropForLayerID('k_cond', lid) for lid in lid_to_keep ]) - rhp_per_cell = np.array( [ self.getSedimentPropForLayerID('rhp', lid) for lid in lid_to_keep ]) + poro0_per_cell = np.array( [ self.getSedimentPropForLayerID('phi', lid,cid) for lid,cid in zip(lid_to_keep,cell_id_to_keep) ] ) + decay_per_cell = np.array( [ self.getSedimentPropForLayerID('decay', lid,cid) for lid,cid in zip(lid_to_keep,cell_id_to_keep) ]) + density_per_cell = np.array( [ self.getSedimentPropForLayerID('solidus', lid,cid) for lid,cid in zip(lid_to_keep,cell_id_to_keep) ]) + cond_per_cell = np.array( [ self.getSedimentPropForLayerID('k_cond', lid,cid) for lid,cid in zip(lid_to_keep,cell_id_to_keep) ]) + rhp_per_cell = np.array( [ self.getSedimentPropForLayerID('rhp', lid,cid) for lid,cid in zip(lid_to_keep,cell_id_to_keep) ]) lid_per_cell = np.array(lid_to_keep) points_cached = [] @@ -183,9 +205,21 @@ def write_hexa_mesh_resqml( self, out_path): points_cached.append(x_original_order[i,:]) hexa_renumbered = [ [point_original_to_cached[i] for i in hexa] for hexa in hexa_to_keep ] + for i,h in enumerate(hexa_renumbered): + minY = np.amin(np.array ( [np.array(points_cached)[hi,1] for hi in h] )) + poro0 = poro0_per_cell[i] + lid0 = lid_to_keep[i] + # if (minY>40000) and poro0 < 0.8 and lid0>=0: + # print("problem A", minY, poro0, i, h) + # breakpoint() + # if (minY<40000) and poro0 > 0.8: + # print("problem B", minY, poro0, i, h) + # breakpoint() + + T_per_vertex = [ self.uh.x.array[reverse_reindex_order[i]] for i in range(self.mesh.geometry.x.shape[0]) if i in p_to_keep ] age_per_vertex = [ self.mesh_vertices_age[reverse_reindex_order[i]] for i in range(self.mesh.geometry.x.shape[0]) if i in p_to_keep ] - + from os import path filename_hex = path.join(out_path, self.modelName+'_hexa_'+str(self.tti)+'.epc') write_hexa_grid_with_properties(filename_hex, np.array(points_cached), hexa_renumbered, "hexamesh", @@ -234,12 +268,22 @@ def getTopOfAsthAtNode(self, tti, node_index): return z0 # - def getSedimentPropForLayerID(self, property, layer_id): + def getSedimentPropForLayerID(self, property, layer_id, node_index): """ """ assert property in ['k_cond', 'rhp', 'phi', 'decay', 'solidus', 'liquidus'], "Unknown property " + property if (layer_id>=0) and (layer_id0.7 and node.Y<40000: + # print("phi", property, phi, node_index, node) + # breakpoint() + # if (property=='phi') and phi<0.7 and node.Y>40000: + # print("phi", property, phi, node_index, node) + # breakpoint() + # phi = self.globalSediments[property][layer_id] + # assert abs(phi-phi0)<1e-6 return phi if (layer_id<=-1) and (layer_id>=-3): lid = -layer_id -1 @@ -257,7 +301,7 @@ def getSedimentPropForLayerID(self, property, layer_id): return [0.5,0.5,0.5][lid] # solid density decay for crust, lith, aest return np.nan - def porosity0ForLayerID(self, layer_id): + def porosity0ForLayerID(self, layer_id, node_index): """Porosity (at surface) conductivity value for the given layer index """ if (layer_id==-1): @@ -267,12 +311,14 @@ def porosity0ForLayerID(self, layer_id): if (layer_id==-3): return 0.0,0.0 # porosity (at surface) of aesth if (layer_id>=0) and (layer_id 0 + node = self.node1D[node_index] + phi = node.sediments.phi[layer_id] + decay = node.sediments.decay[layer_id] return phi, decay return 0.0, 0.0 - def cRhoForLayerID(self, ss): + def cRhoForLayerID(self, ss, node_index): # # prefactor 1000 is the heat capacity.. assumed constant # @@ -283,13 +329,23 @@ def cRhoForLayerID(self, ss): if (ss==-3): return 1000*self.parameters1D.crustsolid if (ss>=0) and (ss=0) and (ss len(self.node1D)-1): + print("cell ID", node_index, len(self.node1D)) + breakpoint() + node = self.node1D[node_index] + kc = node.sediments.k_cond[ss] return kc return self.parameters1D.kCrust - def rhpForLayerID(self, ss): + def rhpForLayerID(self, ss, node_index): """Radiogenic heat production for a layer ID index """ if (ss==-1): @@ -311,7 +373,8 @@ def rhpForLayerID(self, ss): if (ss==-3): return 0 if (ss>=0) and (ss0) else top_of_sediments @@ -430,6 +496,7 @@ def buildHexahedra(self): hexaHedra = [] hex_data_layerID = [] + hex_data_nodeID = [] for q in nodeQuads: for s in range(v_per_n-1): h = [] @@ -451,7 +518,8 @@ def buildHexahedra(self): if (ss>=self.numElemInCrust+self.numElemInLith) and (ss1.0] = 1.0 res = nz * (self.TempBase-self.Temp0) + self.Temp0 - res[x[2,:]250000): + res[i] = 1369 + # Zmax = np.amax(x[2,:]) + # res[x[2,:] single_node: + assert len(interpolationNodes)>0 + if interpolationWeights is None: + interpolationWeights = np.ones([len(interpolationNodes),1]) + assert len(interpolationNodes)==len(interpolationWeights) + wsum = np.sum(np.array(interpolationWeights)) + iWeightNorm = [ w/wsum for w in interpolationWeights] + + node = single_node() + node.__dict__.update(interpolationNodes[0].__dict__) + node.X = np.sum( np.array( [node.X * w for node,w in zip(interpolationNodes,iWeightNorm)] ) ) + node.Y = np.sum( np.array( [node.Y * w for node,w in zip(interpolationNodes,iWeightNorm)] ) ) + + times = range(node.result._depth.shape[1]) + node.subsidence = np.sum( np.array( [ [node.result.seabed(t) for t in times] * w for node,w in zip(interpolationNodes,iWeightNorm)] ) , axis = 0) + node.crust_ls = np.sum( np.array( [ [node.result.crust_thickness(t) for t in times] * w for node,w in zip(interpolationNodes,iWeightNorm)] ) , axis = 0) + node.lith_ls = np.sum( np.array( [ [node.result.lithosphere_thickness(t) for t in times] * w for node,w in zip(interpolationNodes,iWeightNorm)] ) , axis = 0) + + node.beta = np.sum( np.array( [node.beta * w for node,w in zip(interpolationNodes,iWeightNorm)] ) , axis = 0) + node.kAsth = np.sum( np.array( [node.kAsth * w for node,w in zip(interpolationNodes,iWeightNorm)] ) , axis = 0) + node.kLith = np.sum( np.array( [node.kLith * w for node,w in zip(interpolationNodes,iWeightNorm)] ) , axis = 0) + node.depth_out = np.sum([node.result._depth*w for n,w in zip(interpolationNodes[0:1], [1] )], axis=0) + node.temperature_out = np.sum([n.result._temperature*w for n,w in zip(interpolationNodes[0:1], [1] )], axis=0) + + node.sed = np.sum([n.sed*w for n,w in zip(interpolationNodes,iWeightNorm)], axis=0) + node.sed_thickness_ls = node.sed[-1,1,:] - node.sed[0,0,:] + return node class Builder: def __init__(self, parameters: Parameters): diff --git a/tests/test_from_grid.py b/tests/test_from_grid.py new file mode 100644 index 0000000..91c2bc6 --- /dev/null +++ b/tests/test_from_grid.py @@ -0,0 +1,260 @@ + +import os +import multiprocessing +from pathlib import Path +import sys +import numpy as np + +import temperer + + +maps_dir = Path("../data/mapA") + +model = temperer.Model() + +inputs = model.builder.input_horizons_template + +inputs.loc[0]=[0,"0.gri",None,"Onlap"] +inputs.loc[1]=[66,"66.gri",None,"Onlap"] +inputs.loc[2]=[100,"100.gri",None,"Onlap"] +inputs.loc[3]=[163,"163.gri",None,"Erosive"] +inputs.loc[4]=[168,"168.gri",None,"Erosive"] +inputs.loc[5]=[170,"170.gri",None,"Onlap"] +inputs.loc[6]=[182,"182.gri",None,"Erosive"] +model.builder.input_horizons=inputs + + +inc = 2000 +model.builder.define_geometry(maps_dir/"0.gri",xinc=inc,yinc=inc,fformat="irap_binary") + +model.builder.extract_nodes(4,maps_dir) + +from temperer.data import haq87 +model.builder.set_eustatic_sea_level(haq87) + +for i in model.builder.iter_node(): + i.rift=[[182,175]] + + +# simulate every 10 nodes +for index in model.builder.grid.indexing_arr: + if (index[0] % 10 > 0): + pass + else: + if isinstance(model.builder.nodes[index[0]][index[1]],bool) is False: + model.builder.nodes[index[0]][index[1]] = False + if (index[1] % 10 > 0): + pass + else: + if isinstance(model.builder.nodes[index[0]][index[1]],bool) is False: + model.builder.nodes[index[0]][index[1]] = False + +indexer_full_sim = [i.indexer for i in model.builder.iter_node() if i._full_simulation is True] + +for ni in range(len(model.builder.nodes)): + for nj in range(len(model.builder.nodes[ni])): + if model.builder.nodes[ni][nj] is not False: + nn = model.builder.nodes[ni][nj] + if nn.Y>40000: + nn.sediments['phi'] = np.ones([len(nn.sediments['phi']),1]) * 0.5 + # nn.sediments['k_cond'] = np.ones([len(nn.sediments['k_cond']),1]) * 2.2 + + +model.simulator.run(save=False,purge=True) + +# %% +for i in model.builder.iter_node(): + if i is not False: + print(i.result.heatflow(0)) + +# interpolate and extrapolate the missing nodes +# find nearby nodes from the array indexer_full_sim, which is sorted by x-index +import itertools +for ni in range(len(model.builder.nodes)): + for nj in range(len(model.builder.nodes[ni])): + if model.builder.nodes[ni][nj] is False: + closest_x_up = [] + for j in range(ni,len(model.builder.nodes)): + matching_x = [ i[0] for i in indexer_full_sim if i[0]==j ] + closest_x_up = closest_x_up + list(set(matching_x)) + if len(matching_x)>0: + break + closest_x_down = [] + for j in range(ni-1,-1,-1): + matching_x = [ i[0] for i in indexer_full_sim if i[0]==j ] + closest_x_down = closest_x_down + list(set(matching_x)) + if len(matching_x)>0: + break + closest_y_up = [] + for j in range(nj,len(model.builder.nodes[ni])): + matching_y = [ i[1] for i in indexer_full_sim if (i[1]==j and ((i[0] in closest_x_up) or i[0] in closest_x_down)) ] + closest_y_up = closest_y_up + list(set(matching_y)) + if len(matching_y)>0: + break + closest_y_down = [] + for j in range(nj-1,-1,-1): + matching_y = [ i[1] for i in indexer_full_sim if (i[1]==j and (i[0] in closest_x_up or i[0] in closest_x_down) ) ] + closest_y_down = closest_y_down + list(set(matching_y)) + if len(matching_y)>0: + break + + interpolationNodes = [ model.builder.nodes[i[0]][i[1]] for i in itertools.product(closest_x_up+closest_x_down, closest_y_up+closest_y_down) ] + interpolationNodes = [nn for nn in interpolationNodes if nn is not False] + node = temperer.build.interpolateNode(interpolationNodes) + node.X, node.Y = model.builder.grid.location_grid[ni,nj,:] + model.builder.nodes[ni][nj] = node + else: + node = temperer.build.interpolateNode([model.builder.nodes[ni][nj]]) # "interpolate" the node from itself to make sure the same member variables exist at the end + model.builder.nodes[ni][nj] = node + +max_age = model.builder.input_horizons['Age'].to_list()[-1] + +# +# assert that the grid of nodes is fully defined; +# assert that the .crust_ls property has been defined for every node +# +for index in model.builder.grid.indexing_arr: + nn = model.builder.nodes[index[0]][index[1]] + assert nn is not False + assert type(nn)==temperer.build.single_node + assert nn.crust_ls.shape[0] > max_age-1 + if nn.Y>40000: + nn.sediments['phi'] = np.ones([len(nn.sediments['phi']),1]) * 0.50 + # nn.sediments['k_cond'] = np.ones([len(nn.sediments['k_cond']),1]) * 2.2 + +# +# run the 3D simulation +# +from subsheat3D.fixed_mesh_model import UniformNodeGridFixedSizeMeshModel +from subsheat3D.Helpers import NodeGrid + +try: + os.mkdir('out') +except FileExistsError: + pass +try: + os.mkdir('mesh') +except FileExistsError: + pass +try: + os.mkdir('temp') +except FileExistsError: + pass + +# convert to 1D array of nodes and add padding! + +pad = 1 +nodes = [] +for j in range(-pad, model.builder.grid.num_nodes_y+pad): + for i in range(-pad, model.builder.grid.num_nodes_x+pad): + node_i = max(min(i, model.builder.grid.num_nodes_x-1), 0) + node_j = max(min(j, model.builder.grid.num_nodes_y-1), 0) + if (node_i != i) or (node_j != j): + # this is a padded node: create it from the nearest node in the unpadded grid + node_new = temperer.build.interpolateNode([model.builder.nodes[node_j][node_i]]) + node_new.X = model.builder.grid.origin_x + i * model.builder.grid.step_x + node_new.Y = model.builder.grid.origin_y + j * model.builder.grid.step_y + nodes.append(node_new) + else: + nodes.append(model.builder.nodes[node_j][node_i]) + +nodeGrid = NodeGrid(0, 0, model.builder.grid.num_nodes_x+2*pad, model.builder.grid.num_nodes_y+2*pad, 100, 100, 5, 100, 100) +nodeGrid.num_nodes_x = model.builder.grid.num_nodes_x + 2*pad +nodeGrid.num_nodes_y = model.builder.grid.num_nodes_y + 2*pad + + +start_time, end_time = max_age, 0 +modelNamePrefix = 'test1_' +mms2, mms_tti = [], [] +out_dir = './out/' + +no_steps = 4 +dt = 314712e8 / no_steps + +for tti in range(start_time, end_time-1,-1): + rebuild_mesh = (tti==start_time) + + # build_tti = 0 # use the full stack at every time step + build_tti = tti # do sedimentation: update stack positions at time steps + + if rebuild_mesh: + print("Rebuild/reload mesh at tti=", tti) + mm2 = UniformNodeGridFixedSizeMeshModel(nodeGrid, nodes, modelName=modelNamePrefix+str(tti), sedimentsOnly=False) + mm2.buildMesh(build_tti) + else: + print("Re-generating mesh vertices at tti=", tti) + mm2.updateMesh(build_tti) + + mm2.baseFluxMagnitude = 0.04 if (tti>50) else 0.04 + print("===",tti," ",mm2.baseFluxMagnitude,"=========== ") + if ( len(mms2) == 0): + mm2.setupSolverAndSolve(no_steps=no_steps, time_step = dt, skip_setup=False) + else: + mm2.setupSolverAndSolve( no_steps=no_steps, time_step=dt, skip_setup=(not rebuild_mesh)) + if (True): + mm2.writeLayerIDFunction(out_dir+"LayerID-"+str(tti)+".xdmf", tti=tti) + mm2.writeTemperatureFunction(out_dir+"Temperature-"+str(tti)+".xdmf", tti=tti) + mm2.writePoroFunction(out_dir+"Poro0-"+str(tti)+".xdmf", tti=tti) + if (tti==0): + fn = out_dir+"mesh-pos-"+str(tti)+".npy" + np.save(fn, mm2.mesh.geometry.x) + print("np save", fn, mm2.mesh.geometry.x.shape) + fn = out_dir+"T-value-"+str(tti)+".npy" + np.save(fn, mm2.uh.x.array[:]) + fn = out_dir+"cell-pos-"+str(tti)+".npy" + print("cell pos shape", mm2.getCellMidpoints().shape ) + np.save(fn, mm2.getCellMidpoints()) + print("thermal cond shape", mm2.thermalCond.x.array.shape) + fn = out_dir+"k-value-"+str(tti)+".npy" + np.save(fn, mm2.thermalCond.x.array[:]) + fn = out_dir+"phi-value-"+str(tti)+".npy" + np.save(fn, mm2.mean_porosity.x.array[:]) + + mms2.append(mm2) + mms_tti.append(tti) + +EPCfilename = mm2.write_hexa_mesh_resqml("temp/") +print("RESQML model written to: " , EPCfilename) +# read_mesh_resqml(EPCfilename, meshTitle="hexamesh") # test reading of the .epc file + + +# hx = model.builder.grid.num_nodes_x // 2 +# hy = model.builder.grid.num_nodes_y // 2 +hx = model.builder.grid.num_nodes_x - 1 - pad +hy = model.builder.grid.num_nodes_y - 1 - pad +# hx = 1 +# hy = 1 + +nn = model.builder.nodes[hy][hx] +dd = nn.depth_out[:,0] + +node_ind = hy*model.builder.grid.num_nodes_x + hx +v_per_n = int(mm2.mesh_vertices.shape[0]/(model.builder.grid.num_nodes_y*model.builder.grid.num_nodes_x)) + +temp_1d = np.nan_to_num(nn.temperature_out[:,0], nan=5.0) +temp_3d_ind = np.array([ np.where([mm2.mesh_reindex==i])[1][0] for i in range(node_ind*v_per_n, (node_ind+1)*v_per_n) ] ) +dd_mesh = mm2.mesh.geometry.x[temp_3d_ind,2] +temp_3d_mesh = mm2.u_n.x.array[temp_3d_ind] + +temp_1d_at_mesh_pos = np.interp(dd_mesh, dd, temp_1d) +dd_subset = np.where(dd_mesh<5000) +print(f'Max. absolute error in temperature at 3D mesh vertex positions: {np.amax(np.abs(temp_1d_at_mesh_pos-temp_3d_mesh))}') +print(f'Max. absolute error at depths < 5000m: {np.amax(np.abs(temp_1d_at_mesh_pos[dd_subset]-temp_3d_mesh[dd_subset]))}') + + +# when not in ipython: +# import matplotlib as plt +# plt.use('qtagg') +# +# %matplotlib +# import matplotlib as plt +# plt.use('TkAgg') + +# plt.pyplot.plot( dd, temp_1d, label=f'1D simulation'); +# # plt.pyplot.plot( dd, temp_3d, label=f'3D simulation'); +# plt.pyplot.plot( dd_mesh, temp_3d_mesh, 'o', label=f'3D simulation (nodes)'); +# plt.pyplot.legend(loc="lower right", prop={'size': 7}) +# plt.pyplot.show() + + + From eb056e3fe679d9c1bf0cabbd4ada70431ed19b3c Mon Sep 17 00:00:00 2001 From: Adam Cheng <52572642+adamchengtkc@users.noreply.github.com> Date: Wed, 1 Nov 2023 12:50:58 +0000 Subject: [PATCH 10/21] DOC: devcontainer dolfinx --- .devcontainer/Dockerfile | 10 +++++----- .devcontainer/devcontainer.json | 2 +- .gitignore | 6 +++++- CONTRIBUTING.md | 4 ++-- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 154d462..86799ef 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,8 +1,8 @@ -FROM mcr.microsoft.com/devcontainers/base:bullseye +FROM dolfinx/dolfinx:stable # FROM mcr.microsoft.com/devcontainers/base:jammy - ARG DEBIAN_FRONTEND=noninteractive -ARG USER=vscode +# RUN useradd -ms /bin/bash vscode +# ARG USER=vscode RUN DEBIAN_FRONTEND=noninteractive \ && apt-get update \ @@ -28,7 +28,7 @@ RUN DEBIAN_FRONTEND=noninteractive \ # Python and poetry installation USER $USER ARG HOME="/home/$USER" -ARG PYTHON_VERSION=3.11 +ARG PYTHON_VERSION=3.10 # ARG PYTHON_VERSION=3.10 ENV PYENV_ROOT="${HOME}/.pyenv" @@ -42,4 +42,4 @@ RUN echo "done 0" \ && pyenv global ${PYTHON_VERSION} \ && echo "done 3" \ && curl -sSL https://install.python-poetry.org | python3 - \ - && poetry config virtualenvs.in-project true \ No newline at end of file + && poetry config virtualenvs.in-project true diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 6e4cfad..8da29d0 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -11,7 +11,7 @@ // "forwardPorts": [], // 👇 Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "poetry install --with dev --no-interaction", + "postCreateCommand": ["poetry install --with dev --no-interaction","poetry run pip install petsc4py==3.20.0 mpi4py==3.1.5 https://github.com/FEniCS/ufl/archive/refs/tags/2023.2.0.zip https://github.com/FEniCS/ffcx/archive/v0.7.0.zip https://github.com/FEniCS/basix/archive/v0.7.0.zip "], // 👇 Configure tool-specific properties. "customizations": { diff --git a/.gitignore b/.gitignore index 7877cef..188248e 100644 --- a/.gitignore +++ b/.gitignore @@ -62,4 +62,8 @@ exec11/ exec12/ exec13/ -docs/_build \ No newline at end of file +docs/_build + +*.c +*.o +*.so \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 04b38ef..e80c786 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -89,7 +89,7 @@ Standard acronyms to start the commit message with are: ``` API: an (incompatible) API change (will be rare) PERF: performance or bench-marking - BLD: change related to building xtgeo + BLD: change related to building BUG: bug fix FIX: fixes wrt to technical issues, e.g. wrong requirements.txt DEP: deprecate something, or remove a deprecated object @@ -98,7 +98,7 @@ Standard acronyms to start the commit message with are: CLN: code cleanup, maintenance commit (refactoring, typos, PEP, etc.) REV: revert an earlier commit TST: addition or modification of tests - REL: related to releasing xtgeo + REL: related to release ``` ## Type hints From 9c4687a45dd5701e0f288ac6eb337dc2c884d7b1 Mon Sep 17 00:00:00 2001 From: Adam Cheng <52572642+adamchengtkc@users.noreply.github.com> Date: Mon, 6 Nov 2023 12:46:18 +0000 Subject: [PATCH 11/21] ENH: resqpy dep --- poetry.lock | 1128 +++++++++++++++++++++++++++--------------------- pyproject.toml | 3 +- 2 files changed, 627 insertions(+), 504 deletions(-) diff --git a/poetry.lock b/poetry.lock index f540552..c06a334 100644 --- a/poetry.lock +++ b/poetry.lock @@ -233,17 +233,6 @@ files = [ [package.extras] dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] -[[package]] -name = "backcall" -version = "0.2.0" -description = "Specifications for callback functions passed in to an API" -optional = false -python-versions = "*" -files = [ - {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, - {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, -] - [[package]] name = "beautifulsoup4" version = "4.12.2" @@ -455,101 +444,101 @@ numpy = {version = ">1.13.3", markers = "python_version < \"3.12.0.rc1\""} [[package]] name = "charset-normalizer" -version = "3.3.1" +version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.3.1.tar.gz", hash = "sha256:d9137a876020661972ca6eec0766d81aef8a5627df628b664b234b73396e727e"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8aee051c89e13565c6bd366813c386939f8e928af93c29fda4af86d25b73d8f8"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:352a88c3df0d1fa886562384b86f9a9e27563d4704ee0e9d56ec6fcd270ea690"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:223b4d54561c01048f657fa6ce41461d5ad8ff128b9678cfe8b2ecd951e3f8a2"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f861d94c2a450b974b86093c6c027888627b8082f1299dfd5a4bae8e2292821"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1171ef1fc5ab4693c5d151ae0fdad7f7349920eabbaca6271f95969fa0756c2d"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28f512b9a33235545fbbdac6a330a510b63be278a50071a336afc1b78781b147"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0e842112fe3f1a4ffcf64b06dc4c61a88441c2f02f373367f7b4c1aa9be2ad5"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f9bc2ce123637a60ebe819f9fccc614da1bcc05798bbbaf2dd4ec91f3e08846"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f194cce575e59ffe442c10a360182a986535fd90b57f7debfaa5c845c409ecc3"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9a74041ba0bfa9bc9b9bb2cd3238a6ab3b7618e759b41bd15b5f6ad958d17605"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b578cbe580e3b41ad17b1c428f382c814b32a6ce90f2d8e39e2e635d49e498d1"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6db3cfb9b4fcecb4390db154e75b49578c87a3b9979b40cdf90d7e4b945656e1"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:debb633f3f7856f95ad957d9b9c781f8e2c6303ef21724ec94bea2ce2fcbd056"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-win32.whl", hash = "sha256:87071618d3d8ec8b186d53cb6e66955ef2a0e4fa63ccd3709c0c90ac5a43520f"}, - {file = "charset_normalizer-3.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:e372d7dfd154009142631de2d316adad3cc1c36c32a38b16a4751ba78da2a397"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae4070f741f8d809075ef697877fd350ecf0b7c5837ed68738607ee0a2c572cf"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:58e875eb7016fd014c0eea46c6fa92b87b62c0cb31b9feae25cbbe62c919f54d"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dbd95e300367aa0827496fe75a1766d198d34385a58f97683fe6e07f89ca3e3c"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de0b4caa1c8a21394e8ce971997614a17648f94e1cd0640fbd6b4d14cab13a72"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:985c7965f62f6f32bf432e2681173db41336a9c2611693247069288bcb0c7f8b"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a15c1fe6d26e83fd2e5972425a772cca158eae58b05d4a25a4e474c221053e2d"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae55d592b02c4349525b6ed8f74c692509e5adffa842e582c0f861751701a673"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be4d9c2770044a59715eb57c1144dedea7c5d5ae80c68fb9959515037cde2008"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:851cf693fb3aaef71031237cd68699dded198657ec1e76a76eb8be58c03a5d1f"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:31bbaba7218904d2eabecf4feec0d07469284e952a27400f23b6628439439fa7"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:871d045d6ccc181fd863a3cd66ee8e395523ebfbc57f85f91f035f50cee8e3d4"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:501adc5eb6cd5f40a6f77fbd90e5ab915c8fd6e8c614af2db5561e16c600d6f3"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f5fb672c396d826ca16a022ac04c9dce74e00a1c344f6ad1a0fdc1ba1f332213"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-win32.whl", hash = "sha256:bb06098d019766ca16fc915ecaa455c1f1cd594204e7f840cd6258237b5079a8"}, - {file = "charset_normalizer-3.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:8af5a8917b8af42295e86b64903156b4f110a30dca5f3b5aedea123fbd638bff"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7ae8e5142dcc7a49168f4055255dbcced01dc1714a90a21f87448dc8d90617d1"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5b70bab78accbc672f50e878a5b73ca692f45f5b5e25c8066d748c09405e6a55"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ceca5876032362ae73b83347be8b5dbd2d1faf3358deb38c9c88776779b2e2f"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34d95638ff3613849f473afc33f65c401a89f3b9528d0d213c7037c398a51296"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9edbe6a5bf8b56a4a84533ba2b2f489d0046e755c29616ef8830f9e7d9cf5728"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6a02a3c7950cafaadcd46a226ad9e12fc9744652cc69f9e5534f98b47f3bbcf"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10b8dd31e10f32410751b3430996f9807fc4d1587ca69772e2aa940a82ab571a"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edc0202099ea1d82844316604e17d2b175044f9bcb6b398aab781eba957224bd"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b891a2f68e09c5ef989007fac11476ed33c5c9994449a4e2c3386529d703dc8b"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:71ef3b9be10070360f289aea4838c784f8b851be3ba58cf796262b57775c2f14"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:55602981b2dbf8184c098bc10287e8c245e351cd4fdcad050bd7199d5a8bf514"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:46fb9970aa5eeca547d7aa0de5d4b124a288b42eaefac677bde805013c95725c"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:520b7a142d2524f999447b3a0cf95115df81c4f33003c51a6ab637cbda9d0bf4"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-win32.whl", hash = "sha256:8ec8ef42c6cd5856a7613dcd1eaf21e5573b2185263d87d27c8edcae33b62a61"}, - {file = "charset_normalizer-3.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:baec8148d6b8bd5cee1ae138ba658c71f5b03e0d69d5907703e3e1df96db5e41"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63a6f59e2d01310f754c270e4a257426fe5a591dc487f1983b3bbe793cf6bac6"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d6bfc32a68bc0933819cfdfe45f9abc3cae3877e1d90aac7259d57e6e0f85b1"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f3100d86dcd03c03f7e9c3fdb23d92e32abbca07e7c13ebd7ddfbcb06f5991f"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39b70a6f88eebe239fa775190796d55a33cfb6d36b9ffdd37843f7c4c1b5dc67"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e12f8ee80aa35e746230a2af83e81bd6b52daa92a8afaef4fea4a2ce9b9f4fa"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b6cefa579e1237ce198619b76eaa148b71894fb0d6bcf9024460f9bf30fd228"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:61f1e3fb621f5420523abb71f5771a204b33c21d31e7d9d86881b2cffe92c47c"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4f6e2a839f83a6a76854d12dbebde50e4b1afa63e27761549d006fa53e9aa80e"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:1ec937546cad86d0dce5396748bf392bb7b62a9eeb8c66efac60e947697f0e58"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:82ca51ff0fc5b641a2d4e1cc8c5ff108699b7a56d7f3ad6f6da9dbb6f0145b48"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:633968254f8d421e70f91c6ebe71ed0ab140220469cf87a9857e21c16687c034"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-win32.whl", hash = "sha256:c0c72d34e7de5604df0fde3644cc079feee5e55464967d10b24b1de268deceb9"}, - {file = "charset_normalizer-3.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:63accd11149c0f9a99e3bc095bbdb5a464862d77a7e309ad5938fbc8721235ae"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5a3580a4fdc4ac05f9e53c57f965e3594b2f99796231380adb2baaab96e22761"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2465aa50c9299d615d757c1c888bc6fef384b7c4aec81c05a0172b4400f98557"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cb7cd68814308aade9d0c93c5bd2ade9f9441666f8ba5aa9c2d4b389cb5e2a45"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91e43805ccafa0a91831f9cd5443aa34528c0c3f2cc48c4cb3d9a7721053874b"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:854cc74367180beb327ab9d00f964f6d91da06450b0855cbbb09187bcdb02de5"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c15070ebf11b8b7fd1bfff7217e9324963c82dbdf6182ff7050519e350e7ad9f"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c4c99f98fc3a1835af8179dcc9013f93594d0670e2fa80c83aa36346ee763d2"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fb765362688821404ad6cf86772fc54993ec11577cd5a92ac44b4c2ba52155b"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dced27917823df984fe0c80a5c4ad75cf58df0fbfae890bc08004cd3888922a2"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a66bcdf19c1a523e41b8e9d53d0cedbfbac2e93c649a2e9502cb26c014d0980c"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ecd26be9f112c4f96718290c10f4caea6cc798459a3a76636b817a0ed7874e42"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3f70fd716855cd3b855316b226a1ac8bdb3caf4f7ea96edcccc6f484217c9597"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:17a866d61259c7de1bdadef418a37755050ddb4b922df8b356503234fff7932c"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-win32.whl", hash = "sha256:548eefad783ed787b38cb6f9a574bd8664468cc76d1538215d510a3cd41406cb"}, - {file = "charset_normalizer-3.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:45f053a0ece92c734d874861ffe6e3cc92150e32136dd59ab1fb070575189c97"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bc791ec3fd0c4309a753f95bb6c749ef0d8ea3aea91f07ee1cf06b7b02118f2f"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0c8c61fb505c7dad1d251c284e712d4e0372cef3b067f7ddf82a7fa82e1e9a93"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2c092be3885a1b7899cd85ce24acedc1034199d6fca1483fa2c3a35c86e43041"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2000c54c395d9e5e44c99dc7c20a64dc371f777faf8bae4919ad3e99ce5253e"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4cb50a0335382aac15c31b61d8531bc9bb657cfd848b1d7158009472189f3d62"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c30187840d36d0ba2893bc3271a36a517a717f9fd383a98e2697ee890a37c273"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe81b35c33772e56f4b6cf62cf4aedc1762ef7162a31e6ac7fe5e40d0149eb67"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0bf89afcbcf4d1bb2652f6580e5e55a840fdf87384f6063c4a4f0c95e378656"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:06cf46bdff72f58645434d467bf5228080801298fbba19fe268a01b4534467f5"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3c66df3f41abee950d6638adc7eac4730a306b022570f71dd0bd6ba53503ab57"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd805513198304026bd379d1d516afbf6c3c13f4382134a2c526b8b854da1c2e"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:9505dc359edb6a330efcd2be825fdb73ee3e628d9010597aa1aee5aa63442e97"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:31445f38053476a0c4e6d12b047b08ced81e2c7c712e5a1ad97bc913256f91b2"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-win32.whl", hash = "sha256:bd28b31730f0e982ace8663d108e01199098432a30a4c410d06fe08fdb9e93f4"}, - {file = "charset_normalizer-3.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:555fe186da0068d3354cdf4bbcbc609b0ecae4d04c921cc13e209eece7720727"}, - {file = "charset_normalizer-3.3.1-py3-none-any.whl", hash = "sha256:800561453acdecedaac137bf09cd719c7a440b6800ec182f077bb8e7025fb708"}, + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] [[package]] @@ -584,74 +573,66 @@ typing = ["mypy (>=0.990)"] [[package]] name = "contourpy" -version = "1.1.1" +version = "1.2.0" description = "Python library for calculating contours of 2D quadrilateral grids" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "contourpy-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:46e24f5412c948d81736509377e255f6040e94216bf1a9b5ea1eaa9d29f6ec1b"}, - {file = "contourpy-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e48694d6a9c5a26ee85b10130c77a011a4fedf50a7279fa0bdaf44bafb4299d"}, - {file = "contourpy-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a66045af6cf00e19d02191ab578a50cb93b2028c3eefed999793698e9ea768ae"}, - {file = "contourpy-1.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ebf42695f75ee1a952f98ce9775c873e4971732a87334b099dde90b6af6a916"}, - {file = "contourpy-1.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6aec19457617ef468ff091669cca01fa7ea557b12b59a7908b9474bb9674cf0"}, - {file = "contourpy-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:462c59914dc6d81e0b11f37e560b8a7c2dbab6aca4f38be31519d442d6cde1a1"}, - {file = "contourpy-1.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6d0a8efc258659edc5299f9ef32d8d81de8b53b45d67bf4bfa3067f31366764d"}, - {file = "contourpy-1.1.1-cp310-cp310-win32.whl", hash = "sha256:d6ab42f223e58b7dac1bb0af32194a7b9311065583cc75ff59dcf301afd8a431"}, - {file = "contourpy-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:549174b0713d49871c6dee90a4b499d3f12f5e5f69641cd23c50a4542e2ca1eb"}, - {file = "contourpy-1.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:407d864db716a067cc696d61fa1ef6637fedf03606e8417fe2aeed20a061e6b2"}, - {file = "contourpy-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe80c017973e6a4c367e037cb31601044dd55e6bfacd57370674867d15a899b"}, - {file = "contourpy-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e30aaf2b8a2bac57eb7e1650df1b3a4130e8d0c66fc2f861039d507a11760e1b"}, - {file = "contourpy-1.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3de23ca4f381c3770dee6d10ead6fff524d540c0f662e763ad1530bde5112532"}, - {file = "contourpy-1.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:566f0e41df06dfef2431defcfaa155f0acfa1ca4acbf8fd80895b1e7e2ada40e"}, - {file = "contourpy-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b04c2f0adaf255bf756cf08ebef1be132d3c7a06fe6f9877d55640c5e60c72c5"}, - {file = "contourpy-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d0c188ae66b772d9d61d43c6030500344c13e3f73a00d1dc241da896f379bb62"}, - {file = "contourpy-1.1.1-cp311-cp311-win32.whl", hash = "sha256:0683e1ae20dc038075d92e0e0148f09ffcefab120e57f6b4c9c0f477ec171f33"}, - {file = "contourpy-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:8636cd2fc5da0fb102a2504fa2c4bea3cbc149533b345d72cdf0e7a924decc45"}, - {file = "contourpy-1.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:560f1d68a33e89c62da5da4077ba98137a5e4d3a271b29f2f195d0fba2adcb6a"}, - {file = "contourpy-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:24216552104ae8f3b34120ef84825400b16eb6133af2e27a190fdc13529f023e"}, - {file = "contourpy-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56de98a2fb23025882a18b60c7f0ea2d2d70bbbcfcf878f9067234b1c4818442"}, - {file = "contourpy-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:07d6f11dfaf80a84c97f1a5ba50d129d9303c5b4206f776e94037332e298dda8"}, - {file = "contourpy-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1eaac5257a8f8a047248d60e8f9315c6cff58f7803971170d952555ef6344a7"}, - {file = "contourpy-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19557fa407e70f20bfaba7d55b4d97b14f9480856c4fb65812e8a05fe1c6f9bf"}, - {file = "contourpy-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:081f3c0880712e40effc5f4c3b08feca6d064cb8cfbb372ca548105b86fd6c3d"}, - {file = "contourpy-1.1.1-cp312-cp312-win32.whl", hash = "sha256:059c3d2a94b930f4dafe8105bcdc1b21de99b30b51b5bce74c753686de858cb6"}, - {file = "contourpy-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:f44d78b61740e4e8c71db1cf1fd56d9050a4747681c59ec1094750a658ceb970"}, - {file = "contourpy-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:70e5a10f8093d228bb2b552beeb318b8928b8a94763ef03b858ef3612b29395d"}, - {file = "contourpy-1.1.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8394e652925a18ef0091115e3cc191fef350ab6dc3cc417f06da66bf98071ae9"}, - {file = "contourpy-1.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5bd5680f844c3ff0008523a71949a3ff5e4953eb7701b28760805bc9bcff217"}, - {file = "contourpy-1.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66544f853bfa85c0d07a68f6c648b2ec81dafd30f272565c37ab47a33b220684"}, - {file = "contourpy-1.1.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0c02b75acfea5cab07585d25069207e478d12309557f90a61b5a3b4f77f46ce"}, - {file = "contourpy-1.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41339b24471c58dc1499e56783fedc1afa4bb018bcd035cfb0ee2ad2a7501ef8"}, - {file = "contourpy-1.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f29fb0b3f1217dfe9362ec55440d0743fe868497359f2cf93293f4b2701b8251"}, - {file = "contourpy-1.1.1-cp38-cp38-win32.whl", hash = "sha256:f9dc7f933975367251c1b34da882c4f0e0b2e24bb35dc906d2f598a40b72bfc7"}, - {file = "contourpy-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:498e53573e8b94b1caeb9e62d7c2d053c263ebb6aa259c81050766beb50ff8d9"}, - {file = "contourpy-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ba42e3810999a0ddd0439e6e5dbf6d034055cdc72b7c5c839f37a7c274cb4eba"}, - {file = "contourpy-1.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c06e4c6e234fcc65435223c7b2a90f286b7f1b2733058bdf1345d218cc59e34"}, - {file = "contourpy-1.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca6fab080484e419528e98624fb5c4282148b847e3602dc8dbe0cb0669469887"}, - {file = "contourpy-1.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93df44ab351119d14cd1e6b52a5063d3336f0754b72736cc63db59307dabb718"}, - {file = "contourpy-1.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eafbef886566dc1047d7b3d4b14db0d5b7deb99638d8e1be4e23a7c7ac59ff0f"}, - {file = "contourpy-1.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efe0fab26d598e1ec07d72cf03eaeeba8e42b4ecf6b9ccb5a356fde60ff08b85"}, - {file = "contourpy-1.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f08e469821a5e4751c97fcd34bcb586bc243c39c2e39321822060ba902eac49e"}, - {file = "contourpy-1.1.1-cp39-cp39-win32.whl", hash = "sha256:bfc8a5e9238232a45ebc5cb3bfee71f1167064c8d382cadd6076f0d51cff1da0"}, - {file = "contourpy-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:c84fdf3da00c2827d634de4fcf17e3e067490c4aea82833625c4c8e6cdea0887"}, - {file = "contourpy-1.1.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:229a25f68046c5cf8067d6d6351c8b99e40da11b04d8416bf8d2b1d75922521e"}, - {file = "contourpy-1.1.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a10dab5ea1bd4401c9483450b5b0ba5416be799bbd50fc7a6cc5e2a15e03e8a3"}, - {file = "contourpy-1.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4f9147051cb8fdb29a51dc2482d792b3b23e50f8f57e3720ca2e3d438b7adf23"}, - {file = "contourpy-1.1.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a75cc163a5f4531a256f2c523bd80db509a49fc23721b36dd1ef2f60ff41c3cb"}, - {file = "contourpy-1.1.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b53d5769aa1f2d4ea407c65f2d1d08002952fac1d9e9d307aa2e1023554a163"}, - {file = "contourpy-1.1.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:11b836b7dbfb74e049c302bbf74b4b8f6cb9d0b6ca1bf86cfa8ba144aedadd9c"}, - {file = "contourpy-1.1.1.tar.gz", hash = "sha256:96ba37c2e24b7212a77da85004c38e7c4d155d3e72a45eeaf22c1f03f607e8ab"}, + {file = "contourpy-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0274c1cb63625972c0c007ab14dd9ba9e199c36ae1a231ce45d725cbcbfd10a8"}, + {file = "contourpy-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ab459a1cbbf18e8698399c595a01f6dcc5c138220ca3ea9e7e6126232d102bb4"}, + {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fdd887f17c2f4572ce548461e4f96396681212d858cae7bd52ba3310bc6f00f"}, + {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d16edfc3fc09968e09ddffada434b3bf989bf4911535e04eada58469873e28e"}, + {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c203f617abc0dde5792beb586f827021069fb6d403d7f4d5c2b543d87edceb9"}, + {file = "contourpy-1.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b69303ceb2e4d4f146bf82fda78891ef7bcd80c41bf16bfca3d0d7eb545448aa"}, + {file = "contourpy-1.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:884c3f9d42d7218304bc74a8a7693d172685c84bd7ab2bab1ee567b769696df9"}, + {file = "contourpy-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4a1b1208102be6e851f20066bf0e7a96b7d48a07c9b0cfe6d0d4545c2f6cadab"}, + {file = "contourpy-1.2.0-cp310-cp310-win32.whl", hash = "sha256:34b9071c040d6fe45d9826cbbe3727d20d83f1b6110d219b83eb0e2a01d79488"}, + {file = "contourpy-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:bd2f1ae63998da104f16a8b788f685e55d65760cd1929518fd94cd682bf03e41"}, + {file = "contourpy-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dd10c26b4eadae44783c45ad6655220426f971c61d9b239e6f7b16d5cdaaa727"}, + {file = "contourpy-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5c6b28956b7b232ae801406e529ad7b350d3f09a4fde958dfdf3c0520cdde0dd"}, + {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebeac59e9e1eb4b84940d076d9f9a6cec0064e241818bcb6e32124cc5c3e377a"}, + {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:139d8d2e1c1dd52d78682f505e980f592ba53c9f73bd6be102233e358b401063"}, + {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e9dc350fb4c58adc64df3e0703ab076f60aac06e67d48b3848c23647ae4310e"}, + {file = "contourpy-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18fc2b4ed8e4a8fe849d18dce4bd3c7ea637758c6343a1f2bae1e9bd4c9f4686"}, + {file = "contourpy-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:16a7380e943a6d52472096cb7ad5264ecee36ed60888e2a3d3814991a0107286"}, + {file = "contourpy-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8d8faf05be5ec8e02a4d86f616fc2a0322ff4a4ce26c0f09d9f7fb5330a35c95"}, + {file = "contourpy-1.2.0-cp311-cp311-win32.whl", hash = "sha256:67b7f17679fa62ec82b7e3e611c43a016b887bd64fb933b3ae8638583006c6d6"}, + {file = "contourpy-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:99ad97258985328b4f207a5e777c1b44a83bfe7cf1f87b99f9c11d4ee477c4de"}, + {file = "contourpy-1.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:575bcaf957a25d1194903a10bc9f316c136c19f24e0985a2b9b5608bdf5dbfe0"}, + {file = "contourpy-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9e6c93b5b2dbcedad20a2f18ec22cae47da0d705d454308063421a3b290d9ea4"}, + {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:464b423bc2a009088f19bdf1f232299e8b6917963e2b7e1d277da5041f33a779"}, + {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:68ce4788b7d93e47f84edd3f1f95acdcd142ae60bc0e5493bfd120683d2d4316"}, + {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d7d1f8871998cdff5d2ff6a087e5e1780139abe2838e85b0b46b7ae6cc25399"}, + {file = "contourpy-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e739530c662a8d6d42c37c2ed52a6f0932c2d4a3e8c1f90692ad0ce1274abe0"}, + {file = "contourpy-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:247b9d16535acaa766d03037d8e8fb20866d054d3c7fbf6fd1f993f11fc60ca0"}, + {file = "contourpy-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:461e3ae84cd90b30f8d533f07d87c00379644205b1d33a5ea03381edc4b69431"}, + {file = "contourpy-1.2.0-cp312-cp312-win32.whl", hash = "sha256:1c2559d6cffc94890b0529ea7eeecc20d6fadc1539273aa27faf503eb4656d8f"}, + {file = "contourpy-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:491b1917afdd8638a05b611a56d46587d5a632cabead889a5440f7c638bc6ed9"}, + {file = "contourpy-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5fd1810973a375ca0e097dee059c407913ba35723b111df75671a1976efa04bc"}, + {file = "contourpy-1.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:999c71939aad2780f003979b25ac5b8f2df651dac7b38fb8ce6c46ba5abe6ae9"}, + {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7caf9b241464c404613512d5594a6e2ff0cc9cb5615c9475cc1d9b514218ae8"}, + {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:266270c6f6608340f6c9836a0fb9b367be61dde0c9a9a18d5ece97774105ff3e"}, + {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbd50d0a0539ae2e96e537553aff6d02c10ed165ef40c65b0e27e744a0f10af8"}, + {file = "contourpy-1.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11f8d2554e52f459918f7b8e6aa20ec2a3bce35ce95c1f0ef4ba36fbda306df5"}, + {file = "contourpy-1.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ce96dd400486e80ac7d195b2d800b03e3e6a787e2a522bfb83755938465a819e"}, + {file = "contourpy-1.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6d3364b999c62f539cd403f8123ae426da946e142312a514162adb2addd8d808"}, + {file = "contourpy-1.2.0-cp39-cp39-win32.whl", hash = "sha256:1c88dfb9e0c77612febebb6ac69d44a8d81e3dc60f993215425b62c1161353f4"}, + {file = "contourpy-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:78e6ad33cf2e2e80c5dfaaa0beec3d61face0fb650557100ee36db808bfa6843"}, + {file = "contourpy-1.2.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:be16975d94c320432657ad2402f6760990cb640c161ae6da1363051805fa8108"}, + {file = "contourpy-1.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b95a225d4948b26a28c08307a60ac00fb8671b14f2047fc5476613252a129776"}, + {file = "contourpy-1.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d7e03c0f9a4f90dc18d4e77e9ef4ec7b7bbb437f7f675be8e530d65ae6ef956"}, + {file = "contourpy-1.2.0.tar.gz", hash = "sha256:171f311cb758de7da13fc53af221ae47a5877be5a0843a9fe150818c51ed276a"}, ] [package.dependencies] -numpy = {version = ">=1.16,<2.0", markers = "python_version <= \"3.11\""} +numpy = ">=1.20,<2.0" [package.extras] bokeh = ["bokeh", "selenium"] docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] -mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.4.1)", "types-Pillow"] +mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.6.1)", "types-Pillow"] test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] -test-no-images = ["pytest", "pytest-cov", "wurlitzer"] +test-no-images = ["pytest", "pytest-cov", "pytest-xdist", "wurlitzer"] [[package]] name = "coverage" @@ -892,13 +873,13 @@ test = ["pytest (>=6)"] [[package]] name = "executing" -version = "2.0.0" +version = "2.0.1" description = "Get the currently executing AST node of a frame, and other information" optional = false -python-versions = "*" +python-versions = ">=3.5" files = [ - {file = "executing-2.0.0-py2.py3-none-any.whl", hash = "sha256:06df6183df67389625f4e763921c6cf978944721abf3e714000200aab95b0657"}, - {file = "executing-2.0.0.tar.gz", hash = "sha256:0ff053696fdeef426cda5bd18eacd94f82c91f49823a2e9090124212ceea9b08"}, + {file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"}, + {file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"}, ] [package.extras] @@ -920,19 +901,19 @@ devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benc [[package]] name = "filelock" -version = "3.12.4" +version = "3.13.1" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.12.4-py3-none-any.whl", hash = "sha256:08c21d87ded6e2b9da6728c3dff51baf1dcecf973b768ef35bcbc3447edb9ad4"}, - {file = "filelock-3.12.4.tar.gz", hash = "sha256:2e6f249f1f3654291606e046b09f1fd5eac39b360664c27f5aad072012f8bcbd"}, + {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, + {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, ] [package.extras] -docs = ["furo (>=2023.7.26)", "sphinx (>=7.1.2)", "sphinx-autodoc-typehints (>=1.24)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3)", "diff-cover (>=7.7)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-timeout (>=2.1)"] -typing = ["typing-extensions (>=4.7.1)"] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] +typing = ["typing-extensions (>=4.8)"] [[package]] name = "flake8" @@ -952,57 +933,57 @@ pyflakes = ">=3.1.0,<3.2.0" [[package]] name = "fonttools" -version = "4.43.1" +version = "4.44.0" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" files = [ - {file = "fonttools-4.43.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bf11e2cca121df35e295bd34b309046c29476ee739753bc6bc9d5050de319273"}, - {file = "fonttools-4.43.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:10b3922875ffcba636674f406f9ab9a559564fdbaa253d66222019d569db869c"}, - {file = "fonttools-4.43.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f727c3e3d08fd25352ed76cc3cb61486f8ed3f46109edf39e5a60fc9fecf6ca"}, - {file = "fonttools-4.43.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad0b3f6342cfa14be996971ea2b28b125ad681c6277c4cd0fbdb50340220dfb6"}, - {file = "fonttools-4.43.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3b7ad05b2beeebafb86aa01982e9768d61c2232f16470f9d0d8e385798e37184"}, - {file = "fonttools-4.43.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4c54466f642d2116686268c3e5f35ebb10e49b0d48d41a847f0e171c785f7ac7"}, - {file = "fonttools-4.43.1-cp310-cp310-win32.whl", hash = "sha256:1e09da7e8519e336239fbd375156488a4c4945f11c4c5792ee086dd84f784d02"}, - {file = "fonttools-4.43.1-cp310-cp310-win_amd64.whl", hash = "sha256:1cf9e974f63b1080b1d2686180fc1fbfd3bfcfa3e1128695b5de337eb9075cef"}, - {file = "fonttools-4.43.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5db46659cfe4e321158de74c6f71617e65dc92e54980086823a207f1c1c0e24b"}, - {file = "fonttools-4.43.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1952c89a45caceedf2ab2506d9a95756e12b235c7182a7a0fff4f5e52227204f"}, - {file = "fonttools-4.43.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c36da88422e0270fbc7fd959dc9749d31a958506c1d000e16703c2fce43e3d0"}, - {file = "fonttools-4.43.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bbbf8174501285049e64d174e29f9578495e1b3b16c07c31910d55ad57683d8"}, - {file = "fonttools-4.43.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d4071bd1c183b8d0b368cc9ed3c07a0f6eb1bdfc4941c4c024c49a35429ac7cd"}, - {file = "fonttools-4.43.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d21099b411e2006d3c3e1f9aaf339e12037dbf7bf9337faf0e93ec915991f43b"}, - {file = "fonttools-4.43.1-cp311-cp311-win32.whl", hash = "sha256:b84a1c00f832feb9d0585ca8432fba104c819e42ff685fcce83537e2e7e91204"}, - {file = "fonttools-4.43.1-cp311-cp311-win_amd64.whl", hash = "sha256:9a2f0aa6ca7c9bc1058a9d0b35483d4216e0c1bbe3962bc62ce112749954c7b8"}, - {file = "fonttools-4.43.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4d9740e3783c748521e77d3c397dc0662062c88fd93600a3c2087d3d627cd5e5"}, - {file = "fonttools-4.43.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:884ef38a5a2fd47b0c1291647b15f4e88b9de5338ffa24ee52c77d52b4dfd09c"}, - {file = "fonttools-4.43.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9648518ef687ba818db3fcc5d9aae27a369253ac09a81ed25c3867e8657a0680"}, - {file = "fonttools-4.43.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95e974d70238fc2be5f444fa91f6347191d0e914d5d8ae002c9aa189572cc215"}, - {file = "fonttools-4.43.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:34f713dad41aa21c637b4e04fe507c36b986a40f7179dcc86402237e2d39dcd3"}, - {file = "fonttools-4.43.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:360201d46165fc0753229afe785900bc9596ee6974833124f4e5e9f98d0f592b"}, - {file = "fonttools-4.43.1-cp312-cp312-win32.whl", hash = "sha256:bb6d2f8ef81ea076877d76acfb6f9534a9c5f31dc94ba70ad001267ac3a8e56f"}, - {file = "fonttools-4.43.1-cp312-cp312-win_amd64.whl", hash = "sha256:25d3da8a01442cbc1106490eddb6d31d7dffb38c1edbfabbcc8db371b3386d72"}, - {file = "fonttools-4.43.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8da417431bfc9885a505e86ba706f03f598c85f5a9c54f67d63e84b9948ce590"}, - {file = "fonttools-4.43.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:51669b60ee2a4ad6c7fc17539a43ffffc8ef69fd5dbed186a38a79c0ac1f5db7"}, - {file = "fonttools-4.43.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748015d6f28f704e7d95cd3c808b483c5fb87fd3eefe172a9da54746ad56bfb6"}, - {file = "fonttools-4.43.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7a58eb5e736d7cf198eee94844b81c9573102ae5989ebcaa1d1a37acd04b33d"}, - {file = "fonttools-4.43.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6bb5ea9076e0e39defa2c325fc086593ae582088e91c0746bee7a5a197be3da0"}, - {file = "fonttools-4.43.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5f37e31291bf99a63328668bb83b0669f2688f329c4c0d80643acee6e63cd933"}, - {file = "fonttools-4.43.1-cp38-cp38-win32.whl", hash = "sha256:9c60ecfa62839f7184f741d0509b5c039d391c3aff71dc5bc57b87cc305cff3b"}, - {file = "fonttools-4.43.1-cp38-cp38-win_amd64.whl", hash = "sha256:fe9b1ec799b6086460a7480e0f55c447b1aca0a4eecc53e444f639e967348896"}, - {file = "fonttools-4.43.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13a9a185259ed144def3682f74fdcf6596f2294e56fe62dfd2be736674500dba"}, - {file = "fonttools-4.43.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2adca1b46d69dce4a37eecc096fe01a65d81a2f5c13b25ad54d5430ae430b13"}, - {file = "fonttools-4.43.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18eefac1b247049a3a44bcd6e8c8fd8b97f3cad6f728173b5d81dced12d6c477"}, - {file = "fonttools-4.43.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2062542a7565091cea4cc14dd99feff473268b5b8afdee564f7067dd9fff5860"}, - {file = "fonttools-4.43.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18a2477c62a728f4d6e88c45ee9ee0229405e7267d7d79ce1f5ce0f3e9f8ab86"}, - {file = "fonttools-4.43.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a7a06f8d95b7496e53af80d974d63516ffb263a468e614978f3899a6df52d4b3"}, - {file = "fonttools-4.43.1-cp39-cp39-win32.whl", hash = "sha256:10003ebd81fec0192c889e63a9c8c63f88c7d72ae0460b7ba0cd2a1db246e5ad"}, - {file = "fonttools-4.43.1-cp39-cp39-win_amd64.whl", hash = "sha256:e117a92b07407a061cde48158c03587ab97e74e7d73cb65e6aadb17af191162a"}, - {file = "fonttools-4.43.1-py3-none-any.whl", hash = "sha256:4f88cae635bfe4bbbdc29d479a297bb525a94889184bb69fa9560c2d4834ddb9"}, - {file = "fonttools-4.43.1.tar.gz", hash = "sha256:17dbc2eeafb38d5d0e865dcce16e313c58265a6d2d20081c435f84dc5a9d8212"}, + {file = "fonttools-4.44.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1cd1c6bb097e774d68402499ff66185190baaa2629ae2f18515a2c50b93db0c"}, + {file = "fonttools-4.44.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b9eab7f9837fdaa2a10a524fbcc2ec24bf60637c044b6e4a59c3f835b90f0fae"}, + {file = "fonttools-4.44.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f412954275e594f7a51c16f3b3edd850acb0d842fefc33856b63a17e18499a5"}, + {file = "fonttools-4.44.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50d25893885e80a5955186791eed5579f1e75921751539cc1dc3ffd1160b48cf"}, + {file = "fonttools-4.44.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:22ea8aa7b3712450b42b044702bd3a64fd118006bad09a6f94bd1b227088492e"}, + {file = "fonttools-4.44.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df40daa6c03b98652ffe8110ae014fe695437f6e1cb5a07e16ea37f40e73ac86"}, + {file = "fonttools-4.44.0-cp310-cp310-win32.whl", hash = "sha256:bca49da868e8bde569ef36f0cc1b6de21d56bf9c3be185c503b629c19a185287"}, + {file = "fonttools-4.44.0-cp310-cp310-win_amd64.whl", hash = "sha256:dbac86d83d96099890e731cc2af97976ff2c98f4ba432fccde657c5653a32f1c"}, + {file = "fonttools-4.44.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e8ff7d19a6804bfd561cfcec9b4200dd1788e28f7de4be70189801530c47c1b3"}, + {file = "fonttools-4.44.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a8a1fa9a718de0bc026979c93e1e9b55c5efde60d76f91561fd713387573817d"}, + {file = "fonttools-4.44.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05064f95aacdfc06f21e55096c964b2228d942b8675fa26995a2551f6329d2d"}, + {file = "fonttools-4.44.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31b38528f25bc662401e6ffae14b3eb7f1e820892fd80369a37155e3b636a2f4"}, + {file = "fonttools-4.44.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:05d7c4d2c95b9490e669f3cb83918799bf1c838619ac6d3bad9ea017cfc63f2e"}, + {file = "fonttools-4.44.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6999e80a125b0cd8e068d0210b63323f17338038c2ecd2e11b9209ec430fe7f2"}, + {file = "fonttools-4.44.0-cp311-cp311-win32.whl", hash = "sha256:a7aec7f5d14dfcd71fb3ebc299b3f000c21fdc4043079101777ed2042ba5b7c5"}, + {file = "fonttools-4.44.0-cp311-cp311-win_amd64.whl", hash = "sha256:518a945dbfe337744bfff31423c1430303b8813c5275dffb0f2577f0734a1189"}, + {file = "fonttools-4.44.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:59b6ad83cce067d10f4790c037a5904424f45bebb5e7be2eb2db90402f288267"}, + {file = "fonttools-4.44.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c2de1fb18198acd400c45ffe2aef5420c8d55fde903e91cba705596099550f3b"}, + {file = "fonttools-4.44.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84f308b7a8d28208d54315d11d35f9888d6d607673dd4d42d60b463682ee0400"}, + {file = "fonttools-4.44.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66bc6efd829382f7a7e6cf33c2fb32b13edc8a239eb15f32acbf197dce7a0165"}, + {file = "fonttools-4.44.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a8b99713d3a0d0e876b6aecfaada5e7dc9fe979fcd90ef9fa0ba1d9b9aed03f2"}, + {file = "fonttools-4.44.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b63da598d9cbc52e2381f922da0e94d60c0429f92207bd3fb04d112fc82ea7cb"}, + {file = "fonttools-4.44.0-cp312-cp312-win32.whl", hash = "sha256:f611c97678604e302b725f71626edea113a5745a7fb557c958b39edb6add87d5"}, + {file = "fonttools-4.44.0-cp312-cp312-win_amd64.whl", hash = "sha256:58af428746fa73a2edcbf26aff33ac4ef3c11c8d75bb200eaea2f7e888d2de4e"}, + {file = "fonttools-4.44.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9ee8692e23028564c13d924004495f284df8ac016a19f17a87251210e1f1f928"}, + {file = "fonttools-4.44.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dab3d00d27b1a79ae4d4a240e8ceea8af0ff049fd45f05adb4f860d93744110d"}, + {file = "fonttools-4.44.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f53526668beccdb3409c6055a4ffe50987a7f05af6436fa55d61f5e7bd450219"}, + {file = "fonttools-4.44.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3da036b016c975c2d8c69005bdc4d5d16266f948a7fab950244e0f58301996a"}, + {file = "fonttools-4.44.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b99fe8ef4093f672d00841569d2d05691e50334d79f4d9c15c1265d76d5580d2"}, + {file = "fonttools-4.44.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d16d9634ff1e5cea2cf4a8cbda9026f766e4b5f30b48f8180f0e99133d3abfc"}, + {file = "fonttools-4.44.0-cp38-cp38-win32.whl", hash = "sha256:3d29509f6e05e8d725db59c2d8c076223d793e4e35773040be6632a0349f2f97"}, + {file = "fonttools-4.44.0-cp38-cp38-win_amd64.whl", hash = "sha256:d4fa4f4bc8fd86579b8cdbe5e948f35d82c0eda0091c399d009b2a5a6b61c040"}, + {file = "fonttools-4.44.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c794de4086f06ae609b71ac944ec7deb09f34ecf73316fddc041087dd24bba39"}, + {file = "fonttools-4.44.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2db63941fee3122e31a21dd0f5b2138ce9906b661a85b63622421d3654a74ae2"}, + {file = "fonttools-4.44.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb01c49c8aa035d5346f46630209923d4927ed15c2493db38d31da9f811eb70d"}, + {file = "fonttools-4.44.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46c79af80a835410874683b5779b6c1ec1d5a285e11c45b5193e79dd691eb111"}, + {file = "fonttools-4.44.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b6e6aa2d066f8dafd06d8d0799b4944b5d5a1f015dd52ac01bdf2895ebe169a0"}, + {file = "fonttools-4.44.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:63a3112f753baef8c6ac2f5f574bb9ac8001b86c8c0c0380039db47a7f512d20"}, + {file = "fonttools-4.44.0-cp39-cp39-win32.whl", hash = "sha256:54efed22b2799a85475e6840e907c402ba49892c614565dc770aa97a53621b2b"}, + {file = "fonttools-4.44.0-cp39-cp39-win_amd64.whl", hash = "sha256:2e91e19b583961979e2e5a701269d3cfc07418963bee717f8160b0a24332826b"}, + {file = "fonttools-4.44.0-py3-none-any.whl", hash = "sha256:b9beb0fa6ff3ea808ad4a6962d68ac0f140ddab080957b20d9e268e4d67fb335"}, + {file = "fonttools-4.44.0.tar.gz", hash = "sha256:4e90dd81b6e0d97ebfe52c0d12a17a9ef7f305d6bfbb93081265057d6092f252"}, ] [package.extras] -all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.0.0)", "xattr", "zopfli (>=0.1.4)"] +all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] graphite = ["lz4 (>=1.7.4.2)"] interpolatable = ["munkres", "scipy"] lxml = ["lxml (>=4.0,<5)"] @@ -1012,7 +993,7 @@ repacker = ["uharfbuzz (>=0.23.0)"] symfont = ["sympy"] type1 = ["xattr"] ufo = ["fs (>=2.2.0,<3)"] -unicode = ["unicodedata2 (>=15.0.0)"] +unicode = ["unicodedata2 (>=15.1.0)"] woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] [[package]] @@ -1173,42 +1154,40 @@ test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio" [[package]] name = "ipython" -version = "8.16.1" +version = "8.17.2" description = "IPython: Productive Interactive Computing" optional = false python-versions = ">=3.9" files = [ - {file = "ipython-8.16.1-py3-none-any.whl", hash = "sha256:0852469d4d579d9cd613c220af7bf0c9cc251813e12be647cb9d463939db9b1e"}, - {file = "ipython-8.16.1.tar.gz", hash = "sha256:ad52f58fca8f9f848e256c629eff888efc0528c12fe0f8ec14f33205f23ef938"}, + {file = "ipython-8.17.2-py3-none-any.whl", hash = "sha256:1e4d1d666a023e3c93585ba0d8e962867f7a111af322efff6b9c58062b3e5444"}, + {file = "ipython-8.17.2.tar.gz", hash = "sha256:126bb57e1895594bb0d91ea3090bbd39384f6fe87c3d57fd558d0670f50339bb"}, ] [package.dependencies] appnope = {version = "*", markers = "sys_platform == \"darwin\""} -backcall = "*" colorama = {version = "*", markers = "sys_platform == \"win32\""} decorator = "*" exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} jedi = ">=0.16" matplotlib-inline = "*" pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} -pickleshare = "*" prompt-toolkit = ">=3.0.30,<3.0.37 || >3.0.37,<3.1.0" pygments = ">=2.4.0" stack-data = "*" traitlets = ">=5" [package.extras] -all = ["black", "curio", "docrepr", "exceptiongroup", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.21)", "pandas", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] +all = ["black", "curio", "docrepr", "exceptiongroup", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.22)", "pandas", "pickleshare", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio (<0.22)", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] black = ["black"] -doc = ["docrepr", "exceptiongroup", "ipykernel", "matplotlib", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] +doc = ["docrepr", "exceptiongroup", "ipykernel", "matplotlib", "pickleshare", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio (<0.22)", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] kernel = ["ipykernel"] nbconvert = ["nbconvert"] nbformat = ["nbformat"] notebook = ["ipywidgets", "notebook"] parallel = ["ipyparallel"] qtconsole = ["qtconsole"] -test = ["pytest (<7.1)", "pytest-asyncio", "testpath"] -test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.21)", "pandas", "pytest (<7.1)", "pytest-asyncio", "testpath", "trio"] +test = ["pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath"] +test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.22)", "pandas", "pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath", "trio"] [[package]] name = "ipywidgets" @@ -1281,6 +1260,17 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "joblib" +version = "1.3.2" +description = "Lightweight pipelining with Python functions" +optional = false +python-versions = ">=3.7" +files = [ + {file = "joblib-1.3.2-py3-none-any.whl", hash = "sha256:ef4331c65f239985f3f2220ecc87db222f08fd22097a3dd5698f693875f8cbb9"}, + {file = "joblib-1.3.2.tar.gz", hash = "sha256:92f865e621e17784e7955080b6d042489e3b8e294949cc44c6eac304f59772b1"}, +] + [[package]] name = "json5" version = "0.9.14" @@ -1308,13 +1298,13 @@ files = [ [[package]] name = "jsonschema" -version = "4.19.1" +version = "4.19.2" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.8" files = [ - {file = "jsonschema-4.19.1-py3-none-any.whl", hash = "sha256:cd5f1f9ed9444e554b38ba003af06c0a8c2868131e56bfbef0550fb450c0330e"}, - {file = "jsonschema-4.19.1.tar.gz", hash = "sha256:ec84cc37cfa703ef7cd4928db24f9cb31428a5d0fa77747b8b51a847458e0bbf"}, + {file = "jsonschema-4.19.2-py3-none-any.whl", hash = "sha256:eee9e502c788e89cb166d4d37f43084e3b64ab405c795c03d343a4dbc2c810fc"}, + {file = "jsonschema-4.19.2.tar.gz", hash = "sha256:c9ff4d7447eed9592c23a12ccee508baf0dd0d59650615e847feb6cdca74f392"}, ] [package.dependencies] @@ -1373,13 +1363,13 @@ test = ["coverage", "ipykernel (>=6.14)", "mypy", "paramiko", "pre-commit", "pyt [[package]] name = "jupyter-core" -version = "5.4.0" +version = "5.5.0" description = "Jupyter core package. A base package on which Jupyter projects rely." optional = false python-versions = ">=3.8" files = [ - {file = "jupyter_core-5.4.0-py3-none-any.whl", hash = "sha256:66e252f675ac04dcf2feb6ed4afb3cd7f68cf92f483607522dc251f32d471571"}, - {file = "jupyter_core-5.4.0.tar.gz", hash = "sha256:e4b98344bb94ee2e3e6c4519a97d001656009f9cb2b7f2baf15b3c205770011d"}, + {file = "jupyter_core-5.5.0-py3-none-any.whl", hash = "sha256:e11e02cd8ae0a9de5c6c44abf5727df9f2581055afe00b22183f621ba3585805"}, + {file = "jupyter_core-5.5.0.tar.gz", hash = "sha256:880b86053bf298a8724994f95e99b99130659022a4f7f45f563084b6223861d3"}, ] [package.dependencies] @@ -1388,7 +1378,7 @@ pywin32 = {version = ">=300", markers = "sys_platform == \"win32\" and platform_ traitlets = ">=5.3" [package.extras] -docs = ["myst-parser", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "traitlets"] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "traitlets"] test = ["ipykernel", "pre-commit", "pytest", "pytest-cov", "pytest-timeout"] [[package]] @@ -1487,13 +1477,13 @@ test = ["coverage", "jupyter-server (>=2.0.0)", "pytest (>=7.0)", "pytest-cov", [[package]] name = "jupyterlab" -version = "4.0.7" +version = "4.0.8" description = "JupyterLab computational environment" optional = false python-versions = ">=3.8" files = [ - {file = "jupyterlab-4.0.7-py3-none-any.whl", hash = "sha256:08683045117cc495531fdb39c22ababb9aaac6977a45e67cfad20046564c9c7c"}, - {file = "jupyterlab-4.0.7.tar.gz", hash = "sha256:48792efd9f962b2bcda1f87d72168ff122c288b1d97d32109e4a11b33dc862be"}, + {file = "jupyterlab-4.0.8-py3-none-any.whl", hash = "sha256:2ff5aa2a51eb21df241d6011c236e88bd1ff9a5dbb75bebc54472f9c18bfffa4"}, + {file = "jupyterlab-4.0.8.tar.gz", hash = "sha256:c4fe93f977bcc987bd395d7fae5ab02e0c042bf4e0f7c95196f3e2e578c2fb3a"}, ] [package.dependencies] @@ -1511,7 +1501,7 @@ tornado = ">=6.2.0" traitlets = "*" [package.extras] -dev = ["black[jupyter] (==23.7.0)", "build", "bump2version", "coverage", "hatch", "pre-commit", "pytest-cov", "ruff (==0.0.286)"] +dev = ["black[jupyter] (==23.10.1)", "build", "bump2version", "coverage", "hatch", "pre-commit", "pytest-cov", "ruff (==0.0.292)"] docs = ["jsx-lexer", "myst-parser", "pydata-sphinx-theme (>=0.13.0)", "pytest", "pytest-check-links", "pytest-tornasync", "sphinx (>=1.8,<7.2.0)", "sphinx-copybutton"] docs-screenshots = ["altair (==5.0.1)", "ipython (==8.14.0)", "ipywidgets (==8.0.6)", "jupyterlab-geojson (==3.4.0)", "jupyterlab-language-pack-zh-cn (==4.0.post0)", "matplotlib (==3.7.1)", "nbconvert (>=7.0.0)", "pandas (==2.0.2)", "scipy (==1.10.1)", "vega-datasets (==0.9.0)"] test = ["coverage", "pytest (>=7.0)", "pytest-check-links (>=0.7)", "pytest-console-scripts", "pytest-cov", "pytest-jupyter (>=0.5.3)", "pytest-timeout", "pytest-tornasync", "requests", "requests-cache", "virtualenv"] @@ -1676,6 +1666,164 @@ files = [ {file = "kiwisolver-1.4.5.tar.gz", hash = "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec"}, ] +[[package]] +name = "lasio" +version = "0.31" +description = "Read/write well data from Log ASCII Standard (LAS) files" +optional = false +python-versions = ">=3" +files = [ + {file = "lasio-0.31-py2.py3-none-any.whl", hash = "sha256:30204596b0f3eb9118af99a622f177fba2b6c245db0512f9f57c437020599db4"}, + {file = "lasio-0.31.tar.gz", hash = "sha256:5dd3c4baa3f2c89bc95ca8e052dc37fe86363c687199c93fd0fae80c597e63e5"}, +] + +[package.dependencies] +numpy = "*" + +[package.extras] +all = ["chardet", "openpyxl", "pandas"] +test = ["black", "chardet", "codecov", "coverage", "openpyxl", "pandas", "pytest (>=3.6)", "pytest-benchmark", "pytest-cov"] + +[[package]] +name = "llvmlite" +version = "0.41.1" +description = "lightweight wrapper around basic LLVM functionality" +optional = false +python-versions = ">=3.8" +files = [ + {file = "llvmlite-0.41.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1e1029d47ee66d3a0c4d6088641882f75b93db82bd0e6178f7bd744ebce42b9"}, + {file = "llvmlite-0.41.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:150d0bc275a8ac664a705135e639178883293cf08c1a38de3bbaa2f693a0a867"}, + {file = "llvmlite-0.41.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1eee5cf17ec2b4198b509272cf300ee6577229d237c98cc6e63861b08463ddc6"}, + {file = "llvmlite-0.41.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dd0338da625346538f1173a17cabf21d1e315cf387ca21b294ff209d176e244"}, + {file = "llvmlite-0.41.1-cp310-cp310-win32.whl", hash = "sha256:fa1469901a2e100c17eb8fe2678e34bd4255a3576d1a543421356e9c14d6e2ae"}, + {file = "llvmlite-0.41.1-cp310-cp310-win_amd64.whl", hash = "sha256:2b76acee82ea0e9304be6be9d4b3840208d050ea0dcad75b1635fa06e949a0ae"}, + {file = "llvmlite-0.41.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:210e458723436b2469d61b54b453474e09e12a94453c97ea3fbb0742ba5a83d8"}, + {file = "llvmlite-0.41.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:855f280e781d49e0640aef4c4af586831ade8f1a6c4df483fb901cbe1a48d127"}, + {file = "llvmlite-0.41.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b67340c62c93a11fae482910dc29163a50dff3dfa88bc874872d28ee604a83be"}, + {file = "llvmlite-0.41.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2181bb63ef3c607e6403813421b46982c3ac6bfc1f11fa16a13eaafb46f578e6"}, + {file = "llvmlite-0.41.1-cp311-cp311-win_amd64.whl", hash = "sha256:9564c19b31a0434f01d2025b06b44c7ed422f51e719ab5d24ff03b7560066c9a"}, + {file = "llvmlite-0.41.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5940bc901fb0325970415dbede82c0b7f3e35c2d5fd1d5e0047134c2c46b3281"}, + {file = "llvmlite-0.41.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8b0a9a47c28f67a269bb62f6256e63cef28d3c5f13cbae4fab587c3ad506778b"}, + {file = "llvmlite-0.41.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8afdfa6da33f0b4226af8e64cfc2b28986e005528fbf944d0a24a72acfc9432"}, + {file = "llvmlite-0.41.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8454c1133ef701e8c050a59edd85d238ee18bb9a0eb95faf2fca8b909ee3c89a"}, + {file = "llvmlite-0.41.1-cp38-cp38-win32.whl", hash = "sha256:2d92c51e6e9394d503033ffe3292f5bef1566ab73029ec853861f60ad5c925d0"}, + {file = "llvmlite-0.41.1-cp38-cp38-win_amd64.whl", hash = "sha256:df75594e5a4702b032684d5481db3af990b69c249ccb1d32687b8501f0689432"}, + {file = "llvmlite-0.41.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:04725975e5b2af416d685ea0769f4ecc33f97be541e301054c9f741003085802"}, + {file = "llvmlite-0.41.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bf14aa0eb22b58c231243dccf7e7f42f7beec48970f2549b3a6acc737d1a4ba4"}, + {file = "llvmlite-0.41.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92c32356f669e036eb01016e883b22add883c60739bc1ebee3a1cc0249a50828"}, + {file = "llvmlite-0.41.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24091a6b31242bcdd56ae2dbea40007f462260bc9bdf947953acc39dffd54f8f"}, + {file = "llvmlite-0.41.1-cp39-cp39-win32.whl", hash = "sha256:880cb57ca49e862e1cd077104375b9d1dfdc0622596dfa22105f470d7bacb309"}, + {file = "llvmlite-0.41.1-cp39-cp39-win_amd64.whl", hash = "sha256:92f093986ab92e71c9ffe334c002f96defc7986efda18397d0f08534f3ebdc4d"}, + {file = "llvmlite-0.41.1.tar.gz", hash = "sha256:f19f767a018e6ec89608e1f6b13348fa2fcde657151137cb64e56d48598a92db"}, +] + +[[package]] +name = "lxml" +version = "4.9.3" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" +files = [ + {file = "lxml-4.9.3-cp27-cp27m-macosx_11_0_x86_64.whl", hash = "sha256:b0a545b46b526d418eb91754565ba5b63b1c0b12f9bd2f808c852d9b4b2f9b5c"}, + {file = "lxml-4.9.3-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:075b731ddd9e7f68ad24c635374211376aa05a281673ede86cbe1d1b3455279d"}, + {file = "lxml-4.9.3-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1e224d5755dba2f4a9498e150c43792392ac9b5380aa1b845f98a1618c94eeef"}, + {file = "lxml-4.9.3-cp27-cp27m-win32.whl", hash = "sha256:2c74524e179f2ad6d2a4f7caf70e2d96639c0954c943ad601a9e146c76408ed7"}, + {file = "lxml-4.9.3-cp27-cp27m-win_amd64.whl", hash = "sha256:4f1026bc732b6a7f96369f7bfe1a4f2290fb34dce00d8644bc3036fb351a4ca1"}, + {file = "lxml-4.9.3-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0781a98ff5e6586926293e59480b64ddd46282953203c76ae15dbbbf302e8bb"}, + {file = "lxml-4.9.3-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cef2502e7e8a96fe5ad686d60b49e1ab03e438bd9123987994528febd569868e"}, + {file = "lxml-4.9.3-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:b86164d2cff4d3aaa1f04a14685cbc072efd0b4f99ca5708b2ad1b9b5988a991"}, + {file = "lxml-4.9.3-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:42871176e7896d5d45138f6d28751053c711ed4d48d8e30b498da155af39aebd"}, + {file = "lxml-4.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ae8b9c6deb1e634ba4f1930eb67ef6e6bf6a44b6eb5ad605642b2d6d5ed9ce3c"}, + {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:411007c0d88188d9f621b11d252cce90c4a2d1a49db6c068e3c16422f306eab8"}, + {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:cd47b4a0d41d2afa3e58e5bf1f62069255aa2fd6ff5ee41604418ca925911d76"}, + {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e2cb47860da1f7e9a5256254b74ae331687b9672dfa780eed355c4c9c3dbd23"}, + {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1247694b26342a7bf47c02e513d32225ededd18045264d40758abeb3c838a51f"}, + {file = "lxml-4.9.3-cp310-cp310-win32.whl", hash = "sha256:cdb650fc86227eba20de1a29d4b2c1bfe139dc75a0669270033cb2ea3d391b85"}, + {file = "lxml-4.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:97047f0d25cd4bcae81f9ec9dc290ca3e15927c192df17331b53bebe0e3ff96d"}, + {file = "lxml-4.9.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:1f447ea5429b54f9582d4b955f5f1985f278ce5cf169f72eea8afd9502973dd5"}, + {file = "lxml-4.9.3-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:57d6ba0ca2b0c462f339640d22882acc711de224d769edf29962b09f77129cbf"}, + {file = "lxml-4.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:9767e79108424fb6c3edf8f81e6730666a50feb01a328f4a016464a5893f835a"}, + {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:71c52db65e4b56b8ddc5bb89fb2e66c558ed9d1a74a45ceb7dcb20c191c3df2f"}, + {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d73d8ecf8ecf10a3bd007f2192725a34bd62898e8da27eb9d32a58084f93962b"}, + {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0a3d3487f07c1d7f150894c238299934a2a074ef590b583103a45002035be120"}, + {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e28c51fa0ce5674be9f560c6761c1b441631901993f76700b1b30ca6c8378d6"}, + {file = "lxml-4.9.3-cp311-cp311-win32.whl", hash = "sha256:0bfd0767c5c1de2551a120673b72e5d4b628737cb05414f03c3277bf9bed3305"}, + {file = "lxml-4.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:25f32acefac14ef7bd53e4218fe93b804ef6f6b92ffdb4322bb6d49d94cad2bc"}, + {file = "lxml-4.9.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:d3ff32724f98fbbbfa9f49d82852b159e9784d6094983d9a8b7f2ddaebb063d4"}, + {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:48d6ed886b343d11493129e019da91d4039826794a3e3027321c56d9e71505be"}, + {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9a92d3faef50658dd2c5470af249985782bf754c4e18e15afb67d3ab06233f13"}, + {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b4e4bc18382088514ebde9328da057775055940a1f2e18f6ad2d78aa0f3ec5b9"}, + {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fc9b106a1bf918db68619fdcd6d5ad4f972fdd19c01d19bdb6bf63f3589a9ec5"}, + {file = "lxml-4.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:d37017287a7adb6ab77e1c5bee9bcf9660f90ff445042b790402a654d2ad81d8"}, + {file = "lxml-4.9.3-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:56dc1f1ebccc656d1b3ed288f11e27172a01503fc016bcabdcbc0978b19352b7"}, + {file = "lxml-4.9.3-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:578695735c5a3f51569810dfebd05dd6f888147a34f0f98d4bb27e92b76e05c2"}, + {file = "lxml-4.9.3-cp35-cp35m-win32.whl", hash = "sha256:704f61ba8c1283c71b16135caf697557f5ecf3e74d9e453233e4771d68a1f42d"}, + {file = "lxml-4.9.3-cp35-cp35m-win_amd64.whl", hash = "sha256:c41bfca0bd3532d53d16fd34d20806d5c2b1ace22a2f2e4c0008570bf2c58833"}, + {file = "lxml-4.9.3-cp36-cp36m-macosx_11_0_x86_64.whl", hash = "sha256:64f479d719dc9f4c813ad9bb6b28f8390360660b73b2e4beb4cb0ae7104f1c12"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:dd708cf4ee4408cf46a48b108fb9427bfa00b9b85812a9262b5c668af2533ea5"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c31c7462abdf8f2ac0577d9f05279727e698f97ecbb02f17939ea99ae8daa98"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e3cd95e10c2610c360154afdc2f1480aea394f4a4f1ea0a5eacce49640c9b190"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:4930be26af26ac545c3dffb662521d4e6268352866956672231887d18f0eaab2"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4aec80cde9197340bc353d2768e2a75f5f60bacda2bab72ab1dc499589b3878c"}, + {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:14e019fd83b831b2e61baed40cab76222139926b1fb5ed0e79225bc0cae14584"}, + {file = "lxml-4.9.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0c0850c8b02c298d3c7006b23e98249515ac57430e16a166873fc47a5d549287"}, + {file = "lxml-4.9.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:aca086dc5f9ef98c512bac8efea4483eb84abbf926eaeedf7b91479feb092458"}, + {file = "lxml-4.9.3-cp36-cp36m-win32.whl", hash = "sha256:50baa9c1c47efcaef189f31e3d00d697c6d4afda5c3cde0302d063492ff9b477"}, + {file = "lxml-4.9.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bef4e656f7d98aaa3486d2627e7d2df1157d7e88e7efd43a65aa5dd4714916cf"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:46f409a2d60f634fe550f7133ed30ad5321ae2e6630f13657fb9479506b00601"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:4c28a9144688aef80d6ea666c809b4b0e50010a2aca784c97f5e6bf143d9f129"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:141f1d1a9b663c679dc524af3ea1773e618907e96075262726c7612c02b149a4"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:53ace1c1fd5a74ef662f844a0413446c0629d151055340e9893da958a374f70d"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17a753023436a18e27dd7769e798ce302963c236bc4114ceee5b25c18c52c693"}, + {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7d298a1bd60c067ea75d9f684f5f3992c9d6766fadbc0bcedd39750bf344c2f4"}, + {file = "lxml-4.9.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:081d32421db5df44c41b7f08a334a090a545c54ba977e47fd7cc2deece78809a"}, + {file = "lxml-4.9.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:23eed6d7b1a3336ad92d8e39d4bfe09073c31bfe502f20ca5116b2a334f8ec02"}, + {file = "lxml-4.9.3-cp37-cp37m-win32.whl", hash = "sha256:1509dd12b773c02acd154582088820893109f6ca27ef7291b003d0e81666109f"}, + {file = "lxml-4.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:120fa9349a24c7043854c53cae8cec227e1f79195a7493e09e0c12e29f918e52"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4d2d1edbca80b510443f51afd8496be95529db04a509bc8faee49c7b0fb6d2cc"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8d7e43bd40f65f7d97ad8ef5c9b1778943d02f04febef12def25f7583d19baac"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:71d66ee82e7417828af6ecd7db817913cb0cf9d4e61aa0ac1fde0583d84358db"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:6fc3c450eaa0b56f815c7b62f2b7fba7266c4779adcf1cece9e6deb1de7305ce"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65299ea57d82fb91c7f019300d24050c4ddeb7c5a190e076b5f48a2b43d19c42"}, + {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:eadfbbbfb41b44034a4c757fd5d70baccd43296fb894dba0295606a7cf3124aa"}, + {file = "lxml-4.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3e9bdd30efde2b9ccfa9cb5768ba04fe71b018a25ea093379c857c9dad262c40"}, + {file = "lxml-4.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fcdd00edfd0a3001e0181eab3e63bd5c74ad3e67152c84f93f13769a40e073a7"}, + {file = "lxml-4.9.3-cp38-cp38-win32.whl", hash = "sha256:57aba1bbdf450b726d58b2aea5fe47c7875f5afb2c4a23784ed78f19a0462574"}, + {file = "lxml-4.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:92af161ecbdb2883c4593d5ed4815ea71b31fafd7fd05789b23100d081ecac96"}, + {file = "lxml-4.9.3-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:9bb6ad405121241e99a86efff22d3ef469024ce22875a7ae045896ad23ba2340"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8ed74706b26ad100433da4b9d807eae371efaa266ffc3e9191ea436087a9d6a7"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fbf521479bcac1e25a663df882c46a641a9bff6b56dc8b0fafaebd2f66fb231b"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:303bf1edce6ced16bf67a18a1cf8339d0db79577eec5d9a6d4a80f0fb10aa2da"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:5515edd2a6d1a5a70bfcdee23b42ec33425e405c5b351478ab7dc9347228f96e"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:690dafd0b187ed38583a648076865d8c229661ed20e48f2335d68e2cf7dc829d"}, + {file = "lxml-4.9.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b6420a005548ad52154c8ceab4a1290ff78d757f9e5cbc68f8c77089acd3c432"}, + {file = "lxml-4.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bb3bb49c7a6ad9d981d734ef7c7193bc349ac338776a0360cc671eaee89bcf69"}, + {file = "lxml-4.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d27be7405547d1f958b60837dc4c1007da90b8b23f54ba1f8b728c78fdb19d50"}, + {file = "lxml-4.9.3-cp39-cp39-win32.whl", hash = "sha256:8df133a2ea5e74eef5e8fc6f19b9e085f758768a16e9877a60aec455ed2609b2"}, + {file = "lxml-4.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:4dd9a263e845a72eacb60d12401e37c616438ea2e5442885f65082c276dfb2b2"}, + {file = "lxml-4.9.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6689a3d7fd13dc687e9102a27e98ef33730ac4fe37795d5036d18b4d527abd35"}, + {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f6bdac493b949141b733c5345b6ba8f87a226029cbabc7e9e121a413e49441e0"}, + {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:05186a0f1346ae12553d66df1cfce6f251589fea3ad3da4f3ef4e34b2d58c6a3"}, + {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c2006f5c8d28dee289f7020f721354362fa304acbaaf9745751ac4006650254b"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-macosx_11_0_x86_64.whl", hash = "sha256:5c245b783db29c4e4fbbbfc9c5a78be496c9fea25517f90606aa1f6b2b3d5f7b"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4fb960a632a49f2f089d522f70496640fdf1218f1243889da3822e0a9f5f3ba7"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:50670615eaf97227d5dc60de2dc99fb134a7130d310d783314e7724bf163f75d"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9719fe17307a9e814580af1f5c6e05ca593b12fb7e44fe62450a5384dbf61b4b"}, + {file = "lxml-4.9.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:3331bece23c9ee066e0fb3f96c61322b9e0f54d775fccefff4c38ca488de283a"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-macosx_11_0_x86_64.whl", hash = "sha256:ed667f49b11360951e201453fc3967344d0d0263aa415e1619e85ae7fd17b4e0"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8b77946fd508cbf0fccd8e400a7f71d4ac0e1595812e66025bac475a8e811694"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e4da8ca0c0c0aea88fd46be8e44bd49716772358d648cce45fe387f7b92374a7"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fe4bda6bd4340caa6e5cf95e73f8fea5c4bfc55763dd42f1b50a94c1b4a2fbd4"}, + {file = "lxml-4.9.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f3df3db1d336b9356dd3112eae5f5c2b8b377f3bc826848567f10bfddfee77e9"}, + {file = "lxml-4.9.3.tar.gz", hash = "sha256:48628bd53a426c9eb9bc066a923acaa0878d1e86129fd5359aee99285f4eed9c"}, +] + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html5 = ["html5lib"] +htmlsoup = ["BeautifulSoup4"] +source = ["Cython (>=0.29.35)"] + [[package]] name = "markdown-it-py" version = "3.0.0" @@ -1771,52 +1919,51 @@ files = [ [[package]] name = "matplotlib" -version = "3.8.0" +version = "3.8.1" description = "Python plotting package" optional = false python-versions = ">=3.9" files = [ - {file = "matplotlib-3.8.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c4940bad88a932ddc69734274f6fb047207e008389489f2b6f77d9ca485f0e7a"}, - {file = "matplotlib-3.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a33bd3045c7452ca1fa65676d88ba940867880e13e2546abb143035fa9072a9d"}, - {file = "matplotlib-3.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ea6886e93401c22e534bbfd39201ce8931b75502895cfb115cbdbbe2d31f287"}, - {file = "matplotlib-3.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d670b9348e712ec176de225d425f150dc8e37b13010d85233c539b547da0be39"}, - {file = "matplotlib-3.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7b37b74f00c4cb6af908cb9a00779d97d294e89fd2145ad43f0cdc23f635760c"}, - {file = "matplotlib-3.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:0e723f5b96f3cd4aad99103dc93e9e3cdc4f18afdcc76951f4857b46f8e39d2d"}, - {file = "matplotlib-3.8.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5dc945a9cb2deb7d197ba23eb4c210e591d52d77bf0ba27c35fc82dec9fa78d4"}, - {file = "matplotlib-3.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8b5a1bf27d078453aa7b5b27f52580e16360d02df6d3dc9504f3d2ce11f6309"}, - {file = "matplotlib-3.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f25ffb6ad972cdffa7df8e5be4b1e3cadd2f8d43fc72085feb1518006178394"}, - {file = "matplotlib-3.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee482731c8c17d86d9ddb5194d38621f9b0f0d53c99006275a12523ab021732"}, - {file = "matplotlib-3.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:36eafe2128772195b373e1242df28d1b7ec6c04c15b090b8d9e335d55a323900"}, - {file = "matplotlib-3.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:061ee58facb3580cd2d046a6d227fb77e9295599c5ec6ad069f06b5821ad1cfc"}, - {file = "matplotlib-3.8.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3cc3776836d0f4f22654a7f2d2ec2004618d5cf86b7185318381f73b80fd8a2d"}, - {file = "matplotlib-3.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6c49a2bd6981264bddcb8c317b6bd25febcece9e2ebfcbc34e7f4c0c867c09dc"}, - {file = "matplotlib-3.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23ed11654fc83cd6cfdf6170b453e437674a050a452133a064d47f2f1371f8d3"}, - {file = "matplotlib-3.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dae97fdd6996b3a25da8ee43e3fc734fff502f396801063c6b76c20b56683196"}, - {file = "matplotlib-3.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:87df75f528020a6299f76a1d986c0ed4406e3b2bd44bc5e306e46bca7d45e53e"}, - {file = "matplotlib-3.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:90d74a95fe055f73a6cd737beecc1b81c26f2893b7a3751d52b53ff06ca53f36"}, - {file = "matplotlib-3.8.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c3499c312f5def8f362a2bf761d04fa2d452b333f3a9a3f58805273719bf20d9"}, - {file = "matplotlib-3.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31e793c8bd4ea268cc5d3a695c27b30650ec35238626961d73085d5e94b6ab68"}, - {file = "matplotlib-3.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d5ee602ef517a89d1f2c508ca189cfc395dd0b4a08284fb1b97a78eec354644"}, - {file = "matplotlib-3.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5de39dc61ca35342cf409e031f70f18219f2c48380d3886c1cf5ad9f17898e06"}, - {file = "matplotlib-3.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:dd386c80a98b5f51571b9484bf6c6976de383cd2a8cd972b6a9562d85c6d2087"}, - {file = "matplotlib-3.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:f691b4ef47c7384d0936b2e8ebdeb5d526c81d004ad9403dfb9d4c76b9979a93"}, - {file = "matplotlib-3.8.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0b11f354aae62a2aa53ec5bb09946f5f06fc41793e351a04ff60223ea9162955"}, - {file = "matplotlib-3.8.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f54b9fb87ca5acbcdd0f286021bedc162e1425fa5555ebf3b3dfc167b955ad9"}, - {file = "matplotlib-3.8.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:60a6e04dfd77c0d3bcfee61c3cd335fff1b917c2f303b32524cd1235e194ef99"}, - {file = "matplotlib-3.8.0.tar.gz", hash = "sha256:df8505e1c19d5c2c26aff3497a7cbd3ccfc2e97043d1e4db3e76afa399164b69"}, + {file = "matplotlib-3.8.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:e11ab864323fa73ac1b7849688d9671c47a2665242e899785b4db1a375b547e1"}, + {file = "matplotlib-3.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:43a9d40feb63c9e31a0b8b069dcbd74a912f59bdc0095d187126694cd26977e4"}, + {file = "matplotlib-3.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:608ea2951838d391e45dec2e644888db6899c752d3c29e157af9dcefb3d7d8d5"}, + {file = "matplotlib-3.8.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82ec95b02e894561c21e066bd0c716e4b410df141ce9441aa5af6cd937e4ade2"}, + {file = "matplotlib-3.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e3ad1759ad4a5245172c6d32b8ada603a6020d03211524c39d78d25c9a7dc0d2"}, + {file = "matplotlib-3.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:20a0fdfd3ee836179047f3782be060057b878ad37f5abe29edf006a1ff3ecd73"}, + {file = "matplotlib-3.8.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7658b7073c1d6a2922ecc0ed41602410fae88586cb8a54f7a2063d537b6beaf7"}, + {file = "matplotlib-3.8.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf6889643d4560fcc56f9f0941f078e4df0d72a6c3e4ca548841fc13c5642664"}, + {file = "matplotlib-3.8.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff842e27bc6a80de08c40e0bfdce460bd08080e8a94af131162b6a1b8948f2cc"}, + {file = "matplotlib-3.8.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f99d07c0e753717775be7be39ab383453b4d8b629c9fa174596b970c6555890"}, + {file = "matplotlib-3.8.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f34b46dbb1db1f09bfa937cd5853e5f2af232caeeff509c3ab6e43fd33780eae"}, + {file = "matplotlib-3.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:1fcb49b6baf0375281979cbf26695ec10bd1cada1e311893e89533b3b70143e7"}, + {file = "matplotlib-3.8.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e17674ee127f78f26fea237e7f4d5cf910a8be82beb6260fedf358b88075b823"}, + {file = "matplotlib-3.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d921c0270647ab11c3ef283efaaa3d46fd005ba233bfb3aea75231cdf3656de8"}, + {file = "matplotlib-3.8.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2afe7d2f8c9e35e94fbcfcfd9b28f29cb32f0a9068cba469cf907428379c8db9"}, + {file = "matplotlib-3.8.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5a504ff40f81d6233603475a45497a6dca37a873393fa20ae6f7dd6596ef72b"}, + {file = "matplotlib-3.8.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cd54bbf089953140905768ed4626d7223e1ad1d7e2a138410a9c4d3b865ccd80"}, + {file = "matplotlib-3.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:27502d2452208ae784c19504644f09f83742809143bbeae147617640930aa344"}, + {file = "matplotlib-3.8.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:f55fb5ff02d999a100be28bf6ffe826e1867a54c7b465409685332c9dd48ffa5"}, + {file = "matplotlib-3.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:afb72822ae410d62aa1a2920c6563cb5680de9078358f0e9474396c6c3e06be2"}, + {file = "matplotlib-3.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43cf368a4a1d8cbc426944806e5e183cead746647a64d2cdb786441546235967"}, + {file = "matplotlib-3.8.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c54c55457c7f5ea4dfdba0020004fc7667f5c10c8d9b8010d735345acc06c9b8"}, + {file = "matplotlib-3.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e3bb809b743653b5aab5d72ee45c8c937c28e147b0846b0826a54bece898608c"}, + {file = "matplotlib-3.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:c1b0ecaa0d1f4fe1e30f625a2347f0034a89a7d17c39efbb502e554d92ee2f61"}, + {file = "matplotlib-3.8.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ca84deaa38cb64b7dd160ca2046b45f7b5dbff2b0179642e1339fadc337446c9"}, + {file = "matplotlib-3.8.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed3b29f54f6bbf3eaca4cbd23bc260155153ace63b7f597c474fa6fc6f386530"}, + {file = "matplotlib-3.8.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d24c47a1bb47e392fbcd26fe322e4ff3431653ac1e8718e4e147d450ae97a44"}, + {file = "matplotlib-3.8.1.tar.gz", hash = "sha256:044df81c1f6f3a8e52d70c4cfcb44e77ea9632a10929932870dfaa90de94365d"}, ] [package.dependencies] contourpy = ">=1.0.1" cycler = ">=0.10" fonttools = ">=4.22.0" -kiwisolver = ">=1.0.1" +kiwisolver = ">=1.3.1" numpy = ">=1.21,<2" packaging = ">=20.0" -pillow = ">=6.2.0" +pillow = ">=8" pyparsing = ">=2.3.1" python-dateutil = ">=2.7" -setuptools_scm = ">=7" [[package]] name = "matplotlib-inline" @@ -2034,13 +2181,13 @@ test = ["flaky", "ipykernel (>=6.19.3)", "ipython", "ipywidgets", "nbconvert (>= [[package]] name = "nbconvert" -version = "7.9.2" +version = "7.10.0" description = "Converting Jupyter Notebooks" optional = false python-versions = ">=3.8" files = [ - {file = "nbconvert-7.9.2-py3-none-any.whl", hash = "sha256:39fe4b8bdd1b0104fdd86fc8a43a9077ba64c720bda4c6132690d917a0a154ee"}, - {file = "nbconvert-7.9.2.tar.gz", hash = "sha256:e56cc7588acc4f93e2bb5a34ec69028e4941797b2bfaf6462f18a41d1cc258c9"}, + {file = "nbconvert-7.10.0-py3-none-any.whl", hash = "sha256:8cf1d95e569730f136feb85e4bba25bdcf3a63fefb122d854ddff6771c0ac933"}, + {file = "nbconvert-7.10.0.tar.gz", hash = "sha256:4bedff08848626be544de193b7594d98a048073f392178008ff4f171f5e21d26"}, ] [package.dependencies] @@ -2218,6 +2365,40 @@ jupyter-server = ">=1.8,<3" [package.extras] test = ["pytest", "pytest-console-scripts", "pytest-jupyter", "pytest-tornasync"] +[[package]] +name = "numba" +version = "0.58.1" +description = "compiling Python code using LLVM" +optional = false +python-versions = ">=3.8" +files = [ + {file = "numba-0.58.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:07f2fa7e7144aa6f275f27260e73ce0d808d3c62b30cff8906ad1dec12d87bbe"}, + {file = "numba-0.58.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7bf1ddd4f7b9c2306de0384bf3854cac3edd7b4d8dffae2ec1b925e4c436233f"}, + {file = "numba-0.58.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bc2d904d0319d7a5857bd65062340bed627f5bfe9ae4a495aef342f072880d50"}, + {file = "numba-0.58.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4e79b6cc0d2bf064a955934a2e02bf676bc7995ab2db929dbbc62e4c16551be6"}, + {file = "numba-0.58.1-cp310-cp310-win_amd64.whl", hash = "sha256:81fe5b51532478149b5081311b0fd4206959174e660c372b94ed5364cfb37c82"}, + {file = "numba-0.58.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bcecd3fb9df36554b342140a4d77d938a549be635d64caf8bd9ef6c47a47f8aa"}, + {file = "numba-0.58.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a1eaa744f518bbd60e1f7ccddfb8002b3d06bd865b94a5d7eac25028efe0e0ff"}, + {file = "numba-0.58.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bf68df9c307fb0aa81cacd33faccd6e419496fdc621e83f1efce35cdc5e79cac"}, + {file = "numba-0.58.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:55a01e1881120e86d54efdff1be08381886fe9f04fc3006af309c602a72bc44d"}, + {file = "numba-0.58.1-cp311-cp311-win_amd64.whl", hash = "sha256:811305d5dc40ae43c3ace5b192c670c358a89a4d2ae4f86d1665003798ea7a1a"}, + {file = "numba-0.58.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ea5bfcf7d641d351c6a80e8e1826eb4a145d619870016eeaf20bbd71ef5caa22"}, + {file = "numba-0.58.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e63d6aacaae1ba4ef3695f1c2122b30fa3d8ba039c8f517784668075856d79e2"}, + {file = "numba-0.58.1-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6fe7a9d8e3bd996fbe5eac0683227ccef26cba98dae6e5cee2c1894d4b9f16c1"}, + {file = "numba-0.58.1-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:898af055b03f09d33a587e9425500e5be84fc90cd2f80b3fb71c6a4a17a7e354"}, + {file = "numba-0.58.1-cp38-cp38-win_amd64.whl", hash = "sha256:d3e2fe81fe9a59fcd99cc572002101119059d64d31eb6324995ee8b0f144a306"}, + {file = "numba-0.58.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c765aef472a9406a97ea9782116335ad4f9ef5c9f93fc05fd44aab0db486954"}, + {file = "numba-0.58.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9e9356e943617f5e35a74bf56ff6e7cc83e6b1865d5e13cee535d79bf2cae954"}, + {file = "numba-0.58.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:240e7a1ae80eb6b14061dc91263b99dc8d6af9ea45d310751b780888097c1aaa"}, + {file = "numba-0.58.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:45698b995914003f890ad839cfc909eeb9c74921849c712a05405d1a79c50f68"}, + {file = "numba-0.58.1-cp39-cp39-win_amd64.whl", hash = "sha256:bd3dda77955be03ff366eebbfdb39919ce7c2620d86c906203bed92124989032"}, + {file = "numba-0.58.1.tar.gz", hash = "sha256:487ded0633efccd9ca3a46364b40006dbdaca0f95e99b8b83e778d1195ebcbaa"}, +] + +[package.dependencies] +llvmlite = "==0.41.*" +numpy = ">=1.22,<1.27" + [[package]] name = "numexpr" version = "2.8.7" @@ -2324,70 +2505,50 @@ files = [ [[package]] name = "pandas" -version = "2.1.2" +version = "1.5.3" description = "Powerful data structures for data analysis, time series, and statistics" optional = false -python-versions = ">=3.9" +python-versions = ">=3.8" files = [ - {file = "pandas-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:24057459f19db9ebb02984c6fdd164a970b31a95f38e4a49cf7615b36a1b532c"}, - {file = "pandas-2.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a6cf8fcc8a63d333970b950a7331a30544cf59b1a97baf0a7409e09eafc1ac38"}, - {file = "pandas-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ae6ffbd9d614c20d028c7117ee911fc4e266b4dca2065d5c5909e401f8ff683"}, - {file = "pandas-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eff794eeb7883c5aefb1ed572e7ff533ae779f6c6277849eab9e77986e352688"}, - {file = "pandas-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:02954e285e8e2f4006b6f22be6f0df1f1c3c97adbb7ed211c6b483426f20d5c8"}, - {file = "pandas-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:5b40c9f494e1f27588c369b9e4a6ca19cd924b3a0e1ef9ef1a8e30a07a438f43"}, - {file = "pandas-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:08d287b68fd28906a94564f15118a7ca8c242e50ae7f8bd91130c362b2108a81"}, - {file = "pandas-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bbd98dcdcd32f408947afdb3f7434fade6edd408c3077bbce7bd840d654d92c6"}, - {file = "pandas-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e90c95abb3285d06f6e4feedafc134306a8eced93cb78e08cf50e224d5ce22e2"}, - {file = "pandas-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52867d69a54e71666cd184b04e839cff7dfc8ed0cd6b936995117fdae8790b69"}, - {file = "pandas-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8d0382645ede2fde352da2a885aac28ec37d38587864c0689b4b2361d17b1d4c"}, - {file = "pandas-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:65177d1c519b55e5b7f094c660ed357bb7d86e799686bb71653b8a4803d8ff0d"}, - {file = "pandas-2.1.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5aa6b86802e8cf7716bf4b4b5a3c99b12d34e9c6a9d06dad254447a620437931"}, - {file = "pandas-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d594e2ce51b8e0b4074e6644758865dc2bb13fd654450c1eae51201260a539f1"}, - {file = "pandas-2.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3223f997b6d2ebf9c010260cf3d889848a93f5d22bb4d14cd32638b3d8bba7ad"}, - {file = "pandas-2.1.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4944dc004ca6cc701dfa19afb8bdb26ad36b9bed5bcec617d2a11e9cae6902"}, - {file = "pandas-2.1.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3f76280ce8ec216dde336e55b2b82e883401cf466da0fe3be317c03fb8ee7c7d"}, - {file = "pandas-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:7ad20d24acf3a0042512b7e8d8fdc2e827126ed519d6bd1ed8e6c14ec8a2c813"}, - {file = "pandas-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:021f09c15e1381e202d95d4a21ece8e7f2bf1388b6d7e9cae09dfe27bd2043d1"}, - {file = "pandas-2.1.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7f12b2de0060b0b858cfec0016e7d980ae5bae455a1746bfcc70929100ee633"}, - {file = "pandas-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83c166b9bb27c1715bed94495d9598a7f02950b4749dba9349c1dd2cbf10729d"}, - {file = "pandas-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25c9976c17311388fcd953cb3d0697999b2205333f4e11e669d90ff8d830d429"}, - {file = "pandas-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:851b5afbb0d62f6129ae891b533aa508cc357d5892c240c91933d945fff15731"}, - {file = "pandas-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:e78507adcc730533619de07bfdd1c62b2918a68cd4419ea386e28abf7f6a1e5c"}, - {file = "pandas-2.1.2.tar.gz", hash = "sha256:52897edc2774d2779fbeb6880d2cfb305daa0b1a29c16b91f531a18918a6e0f3"}, + {file = "pandas-1.5.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3749077d86e3a2f0ed51367f30bf5b82e131cc0f14260c4d3e499186fccc4406"}, + {file = "pandas-1.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:972d8a45395f2a2d26733eb8d0f629b2f90bebe8e8eddbb8829b180c09639572"}, + {file = "pandas-1.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:50869a35cbb0f2e0cd5ec04b191e7b12ed688874bd05dd777c19b28cbea90996"}, + {file = "pandas-1.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3ac844a0fe00bfaeb2c9b51ab1424e5c8744f89860b138434a363b1f620f354"}, + {file = "pandas-1.5.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a0a56cef15fd1586726dace5616db75ebcfec9179a3a55e78f72c5639fa2a23"}, + {file = "pandas-1.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:478ff646ca42b20376e4ed3fa2e8d7341e8a63105586efe54fa2508ee087f328"}, + {file = "pandas-1.5.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6973549c01ca91ec96199e940495219c887ea815b2083722821f1d7abfa2b4dc"}, + {file = "pandas-1.5.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c39a8da13cede5adcd3be1182883aea1c925476f4e84b2807a46e2775306305d"}, + {file = "pandas-1.5.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f76d097d12c82a535fda9dfe5e8dd4127952b45fea9b0276cb30cca5ea313fbc"}, + {file = "pandas-1.5.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e474390e60ed609cec869b0da796ad94f420bb057d86784191eefc62b65819ae"}, + {file = "pandas-1.5.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f2b952406a1588ad4cad5b3f55f520e82e902388a6d5a4a91baa8d38d23c7f6"}, + {file = "pandas-1.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:bc4c368f42b551bf72fac35c5128963a171b40dce866fb066540eeaf46faa003"}, + {file = "pandas-1.5.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:14e45300521902689a81f3f41386dc86f19b8ba8dd5ac5a3c7010ef8d2932813"}, + {file = "pandas-1.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9842b6f4b8479e41968eced654487258ed81df7d1c9b7b870ceea24ed9459b31"}, + {file = "pandas-1.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:26d9c71772c7afb9d5046e6e9cf42d83dd147b5cf5bcb9d97252077118543792"}, + {file = "pandas-1.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fbcb19d6fceb9e946b3e23258757c7b225ba450990d9ed63ccceeb8cae609f7"}, + {file = "pandas-1.5.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:565fa34a5434d38e9d250af3c12ff931abaf88050551d9fbcdfafca50d62babf"}, + {file = "pandas-1.5.3-cp38-cp38-win32.whl", hash = "sha256:87bd9c03da1ac870a6d2c8902a0e1fd4267ca00f13bc494c9e5a9020920e1d51"}, + {file = "pandas-1.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:41179ce559943d83a9b4bbacb736b04c928b095b5f25dd2b7389eda08f46f373"}, + {file = "pandas-1.5.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c74a62747864ed568f5a82a49a23a8d7fe171d0c69038b38cedf0976831296fa"}, + {file = "pandas-1.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c4c00e0b0597c8e4f59e8d461f797e5d70b4d025880516a8261b2817c47759ee"}, + {file = "pandas-1.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a50d9a4336a9621cab7b8eb3fb11adb82de58f9b91d84c2cd526576b881a0c5a"}, + {file = "pandas-1.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd05f7783b3274aa206a1af06f0ceed3f9b412cf665b7247eacd83be41cf7bf0"}, + {file = "pandas-1.5.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f69c4029613de47816b1bb30ff5ac778686688751a5e9c99ad8c7031f6508e5"}, + {file = "pandas-1.5.3-cp39-cp39-win32.whl", hash = "sha256:7cec0bee9f294e5de5bbfc14d0573f65526071029d036b753ee6507d2a21480a"}, + {file = "pandas-1.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:dfd681c5dc216037e0b0a2c821f5ed99ba9f03ebcf119c7dac0e9a7b960b9ec9"}, + {file = "pandas-1.5.3.tar.gz", hash = "sha256:74a3fd7e5a7ec052f183273dc7b0acd3a863edf7520f5d3a1765c04ffdb3b0b1"}, ] [package.dependencies] numpy = [ - {version = ">=1.22.4,<2", markers = "python_version < \"3.11\""}, - {version = ">=1.23.2,<2", markers = "python_version == \"3.11\""}, + {version = ">=1.23.2", markers = "python_version >= \"3.11\""}, + {version = ">=1.21.0", markers = "python_version >= \"3.10\" and python_version < \"3.11\""}, ] -python-dateutil = ">=2.8.2" +python-dateutil = ">=2.8.1" pytz = ">=2020.1" -tzdata = ">=2022.1" [package.extras] -all = ["PyQt5 (>=5.15.6)", "SQLAlchemy (>=1.4.36)", "beautifulsoup4 (>=4.11.1)", "bottleneck (>=1.3.4)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=0.8.1)", "fsspec (>=2022.05.0)", "gcsfs (>=2022.05.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.8.0)", "matplotlib (>=3.6.1)", "numba (>=0.55.2)", "numexpr (>=2.8.0)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pandas-gbq (>=0.17.5)", "psycopg2 (>=2.9.3)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.5)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)", "pyxlsb (>=1.0.9)", "qtpy (>=2.2.0)", "s3fs (>=2022.05.0)", "scipy (>=1.8.1)", "tables (>=3.7.0)", "tabulate (>=0.8.10)", "xarray (>=2022.03.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)", "zstandard (>=0.17.0)"] -aws = ["s3fs (>=2022.05.0)"] -clipboard = ["PyQt5 (>=5.15.6)", "qtpy (>=2.2.0)"] -compression = ["zstandard (>=0.17.0)"] -computation = ["scipy (>=1.8.1)", "xarray (>=2022.03.0)"] -consortium-standard = ["dataframe-api-compat (>=0.1.7)"] -excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pyxlsb (>=1.0.9)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)"] -feather = ["pyarrow (>=7.0.0)"] -fss = ["fsspec (>=2022.05.0)"] -gcp = ["gcsfs (>=2022.05.0)", "pandas-gbq (>=0.17.5)"] -hdf5 = ["tables (>=3.7.0)"] -html = ["beautifulsoup4 (>=4.11.1)", "html5lib (>=1.1)", "lxml (>=4.8.0)"] -mysql = ["SQLAlchemy (>=1.4.36)", "pymysql (>=1.0.2)"] -output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.8.10)"] -parquet = ["pyarrow (>=7.0.0)"] -performance = ["bottleneck (>=1.3.4)", "numba (>=0.55.2)", "numexpr (>=2.8.0)"] -plot = ["matplotlib (>=3.6.1)"] -postgresql = ["SQLAlchemy (>=1.4.36)", "psycopg2 (>=2.9.3)"] -spss = ["pyreadstat (>=1.1.5)"] -sql-other = ["SQLAlchemy (>=1.4.36)"] -test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"] -xml = ["lxml (>=4.8.0)"] +test = ["hypothesis (>=5.5.3)", "pytest (>=6.0)", "pytest-xdist (>=1.31)"] [[package]] name = "pandocfilters" @@ -2429,17 +2590,6 @@ files = [ [package.dependencies] ptyprocess = ">=0.5" -[[package]] -name = "pickleshare" -version = "0.7.5" -description = "Tiny 'shelve'-like database with concurrency support" -optional = false -python-versions = "*" -files = [ - {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, - {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, -] - [[package]] name = "pillow" version = "10.1.0" @@ -2549,13 +2699,13 @@ files = [ [[package]] name = "prometheus-client" -version = "0.17.1" +version = "0.18.0" description = "Python client for the Prometheus monitoring system." optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "prometheus_client-0.17.1-py3-none-any.whl", hash = "sha256:e537f37160f6807b8202a6fc4764cdd19bac5480ddd3e0d463c3002b34462101"}, - {file = "prometheus_client-0.17.1.tar.gz", hash = "sha256:21e674f39831ae3f8acde238afd9a27a37d0d2fb5a28ea094f0ce25d2cbf2091"}, + {file = "prometheus_client-0.18.0-py3-none-any.whl", hash = "sha256:8de3ae2755f890826f4b6479e5571d4f74ac17a81345fe69a6778fdb92579184"}, + {file = "prometheus_client-0.18.0.tar.gz", hash = "sha256:35f7a8c22139e2bb7ca5a698e92d38145bc8dc74c1c0bf56f25cca886a764e17"}, ] [package.extras] @@ -3014,6 +3164,27 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "resqpy" +version = "4.12.1" +description = "Python API for working with RESQML models" +optional = false +python-versions = ">=3.8.1,<3.12" +files = [ + {file = "resqpy-4.12.1-py3-none-any.whl", hash = "sha256:b76b597e2899971db7016377466a5d5521fdd90aec16456fc4ce473700cec83c"}, + {file = "resqpy-4.12.1.tar.gz", hash = "sha256:0356f6efe219e986cf05594e3ac2d9bf61c15031530896ce807c8e4ff7b9f38f"}, +] + +[package.dependencies] +h5py = ">=3.7,<4.0" +joblib = ">=1.2,<2.0" +lasio = ">=0.31,<0.32" +lxml = ">=4.9,<5.0" +numba = ">=0.56,<1.0" +numpy = ">=1.23,<2.0" +pandas = ">=1.4,<2.0" +scipy = ">=1.9,<2.0" + [[package]] name = "rfc3339-validator" version = "0.1.4" @@ -3073,121 +3244,121 @@ numpy = "<2" [[package]] name = "rpds-py" -version = "0.10.6" +version = "0.12.0" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.8" files = [ - {file = "rpds_py-0.10.6-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:6bdc11f9623870d75692cc33c59804b5a18d7b8a4b79ef0b00b773a27397d1f6"}, - {file = "rpds_py-0.10.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:26857f0f44f0e791f4a266595a7a09d21f6b589580ee0585f330aaccccb836e3"}, - {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7f5e15c953ace2e8dde9824bdab4bec50adb91a5663df08d7d994240ae6fa31"}, - {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61fa268da6e2e1cd350739bb61011121fa550aa2545762e3dc02ea177ee4de35"}, - {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c48f3fbc3e92c7dd6681a258d22f23adc2eb183c8cb1557d2fcc5a024e80b094"}, - {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0503c5b681566e8b722fe8c4c47cce5c7a51f6935d5c7012c4aefe952a35eed"}, - {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:734c41f9f57cc28658d98270d3436dba65bed0cfc730d115b290e970150c540d"}, - {file = "rpds_py-0.10.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a5d7ed104d158c0042a6a73799cf0eb576dfd5fc1ace9c47996e52320c37cb7c"}, - {file = "rpds_py-0.10.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e3df0bc35e746cce42579826b89579d13fd27c3d5319a6afca9893a9b784ff1b"}, - {file = "rpds_py-0.10.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:73e0a78a9b843b8c2128028864901f55190401ba38aae685350cf69b98d9f7c9"}, - {file = "rpds_py-0.10.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5ed505ec6305abd2c2c9586a7b04fbd4baf42d4d684a9c12ec6110deefe2a063"}, - {file = "rpds_py-0.10.6-cp310-none-win32.whl", hash = "sha256:d97dd44683802000277bbf142fd9f6b271746b4846d0acaf0cefa6b2eaf2a7ad"}, - {file = "rpds_py-0.10.6-cp310-none-win_amd64.whl", hash = "sha256:b455492cab07107bfe8711e20cd920cc96003e0da3c1f91297235b1603d2aca7"}, - {file = "rpds_py-0.10.6-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:e8cdd52744f680346ff8c1ecdad5f4d11117e1724d4f4e1874f3a67598821069"}, - {file = "rpds_py-0.10.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66414dafe4326bca200e165c2e789976cab2587ec71beb80f59f4796b786a238"}, - {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc435d059f926fdc5b05822b1be4ff2a3a040f3ae0a7bbbe672babb468944722"}, - {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8e7f2219cb72474571974d29a191714d822e58be1eb171f229732bc6fdedf0ac"}, - {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3953c6926a63f8ea5514644b7afb42659b505ece4183fdaaa8f61d978754349e"}, - {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2bb2e4826be25e72013916eecd3d30f66fd076110de09f0e750163b416500721"}, - {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bf347b495b197992efc81a7408e9a83b931b2f056728529956a4d0858608b80"}, - {file = "rpds_py-0.10.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:102eac53bb0bf0f9a275b438e6cf6904904908562a1463a6fc3323cf47d7a532"}, - {file = "rpds_py-0.10.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40f93086eef235623aa14dbddef1b9fb4b22b99454cb39a8d2e04c994fb9868c"}, - {file = "rpds_py-0.10.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e22260a4741a0e7a206e175232867b48a16e0401ef5bce3c67ca5b9705879066"}, - {file = "rpds_py-0.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f4e56860a5af16a0fcfa070a0a20c42fbb2012eed1eb5ceeddcc7f8079214281"}, - {file = "rpds_py-0.10.6-cp311-none-win32.whl", hash = "sha256:0774a46b38e70fdde0c6ded8d6d73115a7c39d7839a164cc833f170bbf539116"}, - {file = "rpds_py-0.10.6-cp311-none-win_amd64.whl", hash = "sha256:4a5ee600477b918ab345209eddafde9f91c0acd931f3776369585a1c55b04c57"}, - {file = "rpds_py-0.10.6-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:5ee97c683eaface61d38ec9a489e353d36444cdebb128a27fe486a291647aff6"}, - {file = "rpds_py-0.10.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0713631d6e2d6c316c2f7b9320a34f44abb644fc487b77161d1724d883662e31"}, - {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5a53f5998b4bbff1cb2e967e66ab2addc67326a274567697379dd1e326bded7"}, - {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6a555ae3d2e61118a9d3e549737bb4a56ff0cec88a22bd1dfcad5b4e04759175"}, - {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:945eb4b6bb8144909b203a88a35e0a03d22b57aefb06c9b26c6e16d72e5eb0f0"}, - {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:52c215eb46307c25f9fd2771cac8135d14b11a92ae48d17968eda5aa9aaf5071"}, - {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1b3cd23d905589cb205710b3988fc8f46d4a198cf12862887b09d7aaa6bf9b9"}, - {file = "rpds_py-0.10.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64ccc28683666672d7c166ed465c09cee36e306c156e787acef3c0c62f90da5a"}, - {file = "rpds_py-0.10.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:516a611a2de12fbea70c78271e558f725c660ce38e0006f75139ba337d56b1f6"}, - {file = "rpds_py-0.10.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9ff93d3aedef11f9c4540cf347f8bb135dd9323a2fc705633d83210d464c579d"}, - {file = "rpds_py-0.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d858532212f0650be12b6042ff4378dc2efbb7792a286bee4489eaa7ba010586"}, - {file = "rpds_py-0.10.6-cp312-none-win32.whl", hash = "sha256:3c4eff26eddac49d52697a98ea01b0246e44ca82ab09354e94aae8823e8bda02"}, - {file = "rpds_py-0.10.6-cp312-none-win_amd64.whl", hash = "sha256:150eec465dbc9cbca943c8e557a21afdcf9bab8aaabf386c44b794c2f94143d2"}, - {file = "rpds_py-0.10.6-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:cf693eb4a08eccc1a1b636e4392322582db2a47470d52e824b25eca7a3977b53"}, - {file = "rpds_py-0.10.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4134aa2342f9b2ab6c33d5c172e40f9ef802c61bb9ca30d21782f6e035ed0043"}, - {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e782379c2028a3611285a795b89b99a52722946d19fc06f002f8b53e3ea26ea9"}, - {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f6da6d842195fddc1cd34c3da8a40f6e99e4a113918faa5e60bf132f917c247"}, - {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4a9fe992887ac68256c930a2011255bae0bf5ec837475bc6f7edd7c8dfa254e"}, - {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b788276a3c114e9f51e257f2a6f544c32c02dab4aa7a5816b96444e3f9ffc336"}, - {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:caa1afc70a02645809c744eefb7d6ee8fef7e2fad170ffdeacca267fd2674f13"}, - {file = "rpds_py-0.10.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bddd4f91eede9ca5275e70479ed3656e76c8cdaaa1b354e544cbcf94c6fc8ac4"}, - {file = "rpds_py-0.10.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:775049dfa63fb58293990fc59473e659fcafd953bba1d00fc5f0631a8fd61977"}, - {file = "rpds_py-0.10.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:c6c45a2d2b68c51fe3d9352733fe048291e483376c94f7723458cfd7b473136b"}, - {file = "rpds_py-0.10.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0699ab6b8c98df998c3eacf51a3b25864ca93dab157abe358af46dc95ecd9801"}, - {file = "rpds_py-0.10.6-cp38-none-win32.whl", hash = "sha256:ebdab79f42c5961682654b851f3f0fc68e6cc7cd8727c2ac4ffff955154123c1"}, - {file = "rpds_py-0.10.6-cp38-none-win_amd64.whl", hash = "sha256:24656dc36f866c33856baa3ab309da0b6a60f37d25d14be916bd3e79d9f3afcf"}, - {file = "rpds_py-0.10.6-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:0898173249141ee99ffcd45e3829abe7bcee47d941af7434ccbf97717df020e5"}, - {file = "rpds_py-0.10.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9e9184fa6c52a74a5521e3e87badbf9692549c0fcced47443585876fcc47e469"}, - {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5752b761902cd15073a527b51de76bbae63d938dc7c5c4ad1e7d8df10e765138"}, - {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99a57006b4ec39dbfb3ed67e5b27192792ffb0553206a107e4aadb39c5004cd5"}, - {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09586f51a215d17efdb3a5f090d7cbf1633b7f3708f60a044757a5d48a83b393"}, - {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e225a6a14ecf44499aadea165299092ab0cba918bb9ccd9304eab1138844490b"}, - {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2039f8d545f20c4e52713eea51a275e62153ee96c8035a32b2abb772b6fc9e5"}, - {file = "rpds_py-0.10.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:34ad87a831940521d462ac11f1774edf867c34172010f5390b2f06b85dcc6014"}, - {file = "rpds_py-0.10.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dcdc88b6b01015da066da3fb76545e8bb9a6880a5ebf89e0f0b2e3ca557b3ab7"}, - {file = "rpds_py-0.10.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:25860ed5c4e7f5e10c496ea78af46ae8d8468e0be745bd233bab9ca99bfd2647"}, - {file = "rpds_py-0.10.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7854a207ef77319ec457c1eb79c361b48807d252d94348305db4f4b62f40f7f3"}, - {file = "rpds_py-0.10.6-cp39-none-win32.whl", hash = "sha256:e6fcc026a3f27c1282c7ed24b7fcac82cdd70a0e84cc848c0841a3ab1e3dea2d"}, - {file = "rpds_py-0.10.6-cp39-none-win_amd64.whl", hash = "sha256:e98c4c07ee4c4b3acf787e91b27688409d918212dfd34c872201273fdd5a0e18"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:68fe9199184c18d997d2e4293b34327c0009a78599ce703e15cd9a0f47349bba"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:3339eca941568ed52d9ad0f1b8eb9fe0958fa245381747cecf2e9a78a5539c42"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a360cfd0881d36c6dc271992ce1eda65dba5e9368575663de993eeb4523d895f"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:031f76fc87644a234883b51145e43985aa2d0c19b063e91d44379cd2786144f8"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f36a9d751f86455dc5278517e8b65580eeee37d61606183897f122c9e51cef3"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:052a832078943d2b2627aea0d19381f607fe331cc0eb5df01991268253af8417"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:023574366002bf1bd751ebaf3e580aef4a468b3d3c216d2f3f7e16fdabd885ed"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:defa2c0c68734f4a82028c26bcc85e6b92cced99866af118cd6a89b734ad8e0d"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:879fb24304ead6b62dbe5034e7b644b71def53c70e19363f3c3be2705c17a3b4"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:53c43e10d398e365da2d4cc0bcaf0854b79b4c50ee9689652cdc72948e86f487"}, - {file = "rpds_py-0.10.6-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:3777cc9dea0e6c464e4b24760664bd8831738cc582c1d8aacf1c3f546bef3f65"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:40578a6469e5d1df71b006936ce95804edb5df47b520c69cf5af264d462f2cbb"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:cf71343646756a072b85f228d35b1d7407da1669a3de3cf47f8bbafe0c8183a4"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10f32b53f424fc75ff7b713b2edb286fdbfc94bf16317890260a81c2c00385dc"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:81de24a1c51cfb32e1fbf018ab0bdbc79c04c035986526f76c33e3f9e0f3356c"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac17044876e64a8ea20ab132080ddc73b895b4abe9976e263b0e30ee5be7b9c2"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e8a78bd4879bff82daef48c14d5d4057f6856149094848c3ed0ecaf49f5aec2"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78ca33811e1d95cac8c2e49cb86c0fb71f4d8409d8cbea0cb495b6dbddb30a55"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c63c3ef43f0b3fb00571cff6c3967cc261c0ebd14a0a134a12e83bdb8f49f21f"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:7fde6d0e00b2fd0dbbb40c0eeec463ef147819f23725eda58105ba9ca48744f4"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:79edd779cfc46b2e15b0830eecd8b4b93f1a96649bcb502453df471a54ce7977"}, - {file = "rpds_py-0.10.6-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:9164ec8010327ab9af931d7ccd12ab8d8b5dc2f4c6a16cbdd9d087861eaaefa1"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d29ddefeab1791e3c751e0189d5f4b3dbc0bbe033b06e9c333dca1f99e1d523e"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:30adb75ecd7c2a52f5e76af50644b3e0b5ba036321c390b8e7ec1bb2a16dd43c"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd609fafdcdde6e67a139898196698af37438b035b25ad63704fd9097d9a3482"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6eef672de005736a6efd565577101277db6057f65640a813de6c2707dc69f396"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cf4393c7b41abbf07c88eb83e8af5013606b1cdb7f6bc96b1b3536b53a574b8"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad857f42831e5b8d41a32437f88d86ead6c191455a3499c4b6d15e007936d4cf"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d7360573f1e046cb3b0dceeb8864025aa78d98be4bb69f067ec1c40a9e2d9df"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d08f63561c8a695afec4975fae445245386d645e3e446e6f260e81663bfd2e38"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:f0f17f2ce0f3529177a5fff5525204fad7b43dd437d017dd0317f2746773443d"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:442626328600bde1d09dc3bb00434f5374948838ce75c41a52152615689f9403"}, - {file = "rpds_py-0.10.6-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e9616f5bd2595f7f4a04b67039d890348ab826e943a9bfdbe4938d0eba606971"}, - {file = "rpds_py-0.10.6.tar.gz", hash = "sha256:4ce5a708d65a8dbf3748d2474b580d606b1b9f91b5c6ab2a316e0b0cf7a4ba50"}, + {file = "rpds_py-0.12.0-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:c694bee70ece3b232df4678448fdda245fd3b1bb4ba481fb6cd20e13bb784c46"}, + {file = "rpds_py-0.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:30e5ce9f501fb1f970e4a59098028cf20676dee64fc496d55c33e04bbbee097d"}, + {file = "rpds_py-0.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d72a4315514e5a0b9837a086cb433b004eea630afb0cc129de76d77654a9606f"}, + {file = "rpds_py-0.12.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eebaf8c76c39604d52852366249ab807fe6f7a3ffb0dd5484b9944917244cdbe"}, + {file = "rpds_py-0.12.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a239303acb0315091d54c7ff36712dba24554993b9a93941cf301391d8a997ee"}, + {file = "rpds_py-0.12.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ced40cdbb6dd47a032725a038896cceae9ce267d340f59508b23537f05455431"}, + {file = "rpds_py-0.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c8c0226c71bd0ce9892eaf6afa77ae8f43a3d9313124a03df0b389c01f832de"}, + {file = "rpds_py-0.12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b8e11715178f3608874508f08e990d3771e0b8c66c73eb4e183038d600a9b274"}, + {file = "rpds_py-0.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5210a0018c7e09c75fa788648617ebba861ae242944111d3079034e14498223f"}, + {file = "rpds_py-0.12.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:171d9a159f1b2f42a42a64a985e4ba46fc7268c78299272ceba970743a67ee50"}, + {file = "rpds_py-0.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:57ec6baec231bb19bb5fd5fc7bae21231860a1605174b11585660236627e390e"}, + {file = "rpds_py-0.12.0-cp310-none-win32.whl", hash = "sha256:7188ddc1a8887194f984fa4110d5a3d5b9b5cd35f6bafdff1b649049cbc0ce29"}, + {file = "rpds_py-0.12.0-cp310-none-win_amd64.whl", hash = "sha256:1e04581c6117ad9479b6cfae313e212fe0dfa226ac727755f0d539cd54792963"}, + {file = "rpds_py-0.12.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:0a38612d07a36138507d69646c470aedbfe2b75b43a4643f7bd8e51e52779624"}, + {file = "rpds_py-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f12d69d568f5647ec503b64932874dade5a20255736c89936bf690951a5e79f5"}, + {file = "rpds_py-0.12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f8a1d990dc198a6c68ec3d9a637ba1ce489b38cbfb65440a27901afbc5df575"}, + {file = "rpds_py-0.12.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8c567c664fc2f44130a20edac73e0a867f8e012bf7370276f15c6adc3586c37c"}, + {file = "rpds_py-0.12.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0e9e976e0dbed4f51c56db10831c9623d0fd67aac02853fe5476262e5a22acb7"}, + {file = "rpds_py-0.12.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:efddca2d02254a52078c35cadad34762adbae3ff01c6b0c7787b59d038b63e0d"}, + {file = "rpds_py-0.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9e7f29c00577aff6b318681e730a519b235af292732a149337f6aaa4d1c5e31"}, + {file = "rpds_py-0.12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:389c0e38358fdc4e38e9995e7291269a3aead7acfcf8942010ee7bc5baee091c"}, + {file = "rpds_py-0.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:33ab498f9ac30598b6406e2be1b45fd231195b83d948ebd4bd77f337cb6a2bff"}, + {file = "rpds_py-0.12.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d56b1cd606ba4cedd64bb43479d56580e147c6ef3f5d1c5e64203a1adab784a2"}, + {file = "rpds_py-0.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1fa73ed22c40a1bec98d7c93b5659cd35abcfa5a0a95ce876b91adbda170537c"}, + {file = "rpds_py-0.12.0-cp311-none-win32.whl", hash = "sha256:dbc25baa6abb205766fb8606f8263b02c3503a55957fcb4576a6bb0a59d37d10"}, + {file = "rpds_py-0.12.0-cp311-none-win_amd64.whl", hash = "sha256:c6b52b7028b547866c2413f614ee306c2d4eafdd444b1ff656bf3295bf1484aa"}, + {file = "rpds_py-0.12.0-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:9620650c364c01ed5b497dcae7c3d4b948daeae6e1883ae185fef1c927b6b534"}, + {file = "rpds_py-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2124f9e645a94ab7c853bc0a3644e0ca8ffbe5bb2d72db49aef8f9ec1c285733"}, + {file = "rpds_py-0.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281c8b219d4f4b3581b918b816764098d04964915b2f272d1476654143801aa2"}, + {file = "rpds_py-0.12.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:27ccc93c7457ef890b0dd31564d2a05e1aca330623c942b7e818e9e7c2669ee4"}, + {file = "rpds_py-0.12.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1c562a9bb72244fa767d1c1ab55ca1d92dd5f7c4d77878fee5483a22ffac808"}, + {file = "rpds_py-0.12.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e57919c32ee295a2fca458bb73e4b20b05c115627f96f95a10f9f5acbd61172d"}, + {file = "rpds_py-0.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa35ad36440aaf1ac8332b4a4a433d4acd28f1613f0d480995f5cfd3580e90b7"}, + {file = "rpds_py-0.12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e6aea5c0eb5b0faf52c7b5c4a47c8bb64437173be97227c819ffa31801fa4e34"}, + {file = "rpds_py-0.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:81cf9d306c04df1b45971c13167dc3bad625808aa01281d55f3cf852dde0e206"}, + {file = "rpds_py-0.12.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:08e6e7ff286254016b945e1ab632ee843e43d45e40683b66dd12b73791366dd1"}, + {file = "rpds_py-0.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4d0a675a7acbbc16179188d8c6d0afb8628604fc1241faf41007255957335a0b"}, + {file = "rpds_py-0.12.0-cp312-none-win32.whl", hash = "sha256:b2287c09482949e0ca0c0eb68b2aca6cf57f8af8c6dfd29dcd3bc45f17b57978"}, + {file = "rpds_py-0.12.0-cp312-none-win_amd64.whl", hash = "sha256:8015835494b21aa7abd3b43fdea0614ee35ef6b03db7ecba9beb58eadf01c24f"}, + {file = "rpds_py-0.12.0-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:6174d6ad6b58a6bcf67afbbf1723420a53d06c4b89f4c50763d6fa0a6ac9afd2"}, + {file = "rpds_py-0.12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a689e1ded7137552bea36305a7a16ad2b40be511740b80748d3140614993db98"}, + {file = "rpds_py-0.12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f45321224144c25a62052035ce96cbcf264667bcb0d81823b1bbc22c4addd194"}, + {file = "rpds_py-0.12.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aa32205358a76bf578854bf31698a86dc8b2cb591fd1d79a833283f4a403f04b"}, + {file = "rpds_py-0.12.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91bd2b7cf0f4d252eec8b7046fa6a43cee17e8acdfc00eaa8b3dbf2f9a59d061"}, + {file = "rpds_py-0.12.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3acadbab8b59f63b87b518e09c4c64b142e7286b9ca7a208107d6f9f4c393c5c"}, + {file = "rpds_py-0.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:429349a510da82c85431f0f3e66212d83efe9fd2850f50f339341b6532c62fe4"}, + {file = "rpds_py-0.12.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05942656cb2cb4989cd50ced52df16be94d344eae5097e8583966a1d27da73a5"}, + {file = "rpds_py-0.12.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:0c5441b7626c29dbd54a3f6f3713ec8e956b009f419ffdaaa3c80eaf98ddb523"}, + {file = "rpds_py-0.12.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:b6b0e17d39d21698185097652c611f9cf30f7c56ccec189789920e3e7f1cee56"}, + {file = "rpds_py-0.12.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3b7a64d43e2a1fa2dd46b678e00cabd9a49ebb123b339ce799204c44a593ae1c"}, + {file = "rpds_py-0.12.0-cp38-none-win32.whl", hash = "sha256:e5bbe011a2cea9060fef1bb3d668a2fd8432b8888e6d92e74c9c794d3c101595"}, + {file = "rpds_py-0.12.0-cp38-none-win_amd64.whl", hash = "sha256:bec29b801b4adbf388314c0d050e851d53762ab424af22657021ce4b6eb41543"}, + {file = "rpds_py-0.12.0-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:1096ca0bf2d3426cbe79d4ccc91dc5aaa73629b08ea2d8467375fad8447ce11a"}, + {file = "rpds_py-0.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48aa98987d54a46e13e6954880056c204700c65616af4395d1f0639eba11764b"}, + {file = "rpds_py-0.12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7979d90ee2190d000129598c2b0c82f13053dba432b94e45e68253b09bb1f0f6"}, + {file = "rpds_py-0.12.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:88857060b690a57d2ea8569bca58758143c8faa4639fb17d745ce60ff84c867e"}, + {file = "rpds_py-0.12.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4eb74d44776b0fb0782560ea84d986dffec8ddd94947f383eba2284b0f32e35e"}, + {file = "rpds_py-0.12.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f62581d7e884dd01ee1707b7c21148f61f2febb7de092ae2f108743fcbef5985"}, + {file = "rpds_py-0.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f5dcb658d597410bb7c967c1d24eaf9377b0d621358cbe9d2ff804e5dd12e81"}, + {file = "rpds_py-0.12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bf9acce44e967a5103fcd820fc7580c7b0ab8583eec4e2051aec560f7b31a63"}, + {file = "rpds_py-0.12.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:240687b5be0f91fbde4936a329c9b7589d9259742766f74de575e1b2046575e4"}, + {file = "rpds_py-0.12.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:25740fb56e8bd37692ed380e15ec734be44d7c71974d8993f452b4527814601e"}, + {file = "rpds_py-0.12.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a54917b7e9cd3a67e429a630e237a90b096e0ba18897bfb99ee8bd1068a5fea0"}, + {file = "rpds_py-0.12.0-cp39-none-win32.whl", hash = "sha256:b92aafcfab3d41580d54aca35a8057341f1cfc7c9af9e8bdfc652f83a20ced31"}, + {file = "rpds_py-0.12.0-cp39-none-win_amd64.whl", hash = "sha256:cd316dbcc74c76266ba94eb021b0cc090b97cca122f50bd7a845f587ff4bf03f"}, + {file = "rpds_py-0.12.0-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:0853da3d5e9bc6a07b2486054a410b7b03f34046c123c6561b535bb48cc509e1"}, + {file = "rpds_py-0.12.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:cb41ad20064e18a900dd427d7cf41cfaec83bcd1184001f3d91a1f76b3fcea4e"}, + {file = "rpds_py-0.12.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b710bf7e7ae61957d5c4026b486be593ed3ec3dca3e5be15e0f6d8cf5d0a4990"}, + {file = "rpds_py-0.12.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a952ae3eb460c6712388ac2ec706d24b0e651b9396d90c9a9e0a69eb27737fdc"}, + {file = "rpds_py-0.12.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0bedd91ae1dd142a4dc15970ed2c729ff6c73f33a40fa84ed0cdbf55de87c777"}, + {file = "rpds_py-0.12.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:761531076df51309075133a6bc1db02d98ec7f66e22b064b1d513bc909f29743"}, + {file = "rpds_py-0.12.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2baa6be130e8a00b6cbb9f18a33611ec150b4537f8563bddadb54c1b74b8193"}, + {file = "rpds_py-0.12.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f05450fa1cd7c525c0b9d1a7916e595d3041ac0afbed2ff6926e5afb6a781b7f"}, + {file = "rpds_py-0.12.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:81c4d1a3a564775c44732b94135d06e33417e829ff25226c164664f4a1046213"}, + {file = "rpds_py-0.12.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:e888be685fa42d8b8a3d3911d5604d14db87538aa7d0b29b1a7ea80d354c732d"}, + {file = "rpds_py-0.12.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6f8d7fe73d1816eeb5378409adc658f9525ecbfaf9e1ede1e2d67a338b0c7348"}, + {file = "rpds_py-0.12.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:0831d3ecdea22e4559cc1793f22e77067c9d8c451d55ae6a75bf1d116a8e7f42"}, + {file = "rpds_py-0.12.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:513ccbf7420c30e283c25c82d5a8f439d625a838d3ba69e79a110c260c46813f"}, + {file = "rpds_py-0.12.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:301bd744a1adaa2f6a5e06c98f1ac2b6f8dc31a5c23b838f862d65e32fca0d4b"}, + {file = "rpds_py-0.12.0-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8832a4f83d4782a8f5a7b831c47e8ffe164e43c2c148c8160ed9a6d630bc02a"}, + {file = "rpds_py-0.12.0-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b2416ed743ec5debcf61e1242e012652a4348de14ecc7df3512da072b074440"}, + {file = "rpds_py-0.12.0-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35585a8cb5917161f42c2104567bb83a1d96194095fc54a543113ed5df9fa436"}, + {file = "rpds_py-0.12.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d389ff1e95b6e46ebedccf7fd1fadd10559add595ac6a7c2ea730268325f832c"}, + {file = "rpds_py-0.12.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9b007c2444705a2dc4a525964fd4dd28c3320b19b3410da6517cab28716f27d3"}, + {file = "rpds_py-0.12.0-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:188912b22b6c8225f4c4ffa020a2baa6ad8fabb3c141a12dbe6edbb34e7f1425"}, + {file = "rpds_py-0.12.0-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b4cf9ab9a0ae0cb122685209806d3f1dcb63b9fccdf1424fb42a129dc8c2faa"}, + {file = "rpds_py-0.12.0-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:2d34a5450a402b00d20aeb7632489ffa2556ca7b26f4a63c35f6fccae1977427"}, + {file = "rpds_py-0.12.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:466030a42724780794dea71eb32db83cc51214d66ab3fb3156edd88b9c8f0d78"}, + {file = "rpds_py-0.12.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:68172622a5a57deb079a2c78511c40f91193548e8ab342c31e8cb0764d362459"}, + {file = "rpds_py-0.12.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54cdfcda59251b9c2f87a05d038c2ae02121219a04d4a1e6fc345794295bdc07"}, + {file = "rpds_py-0.12.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6b75b912a0baa033350367a8a07a8b2d44fd5b90c890bfbd063a8a5f945f644b"}, + {file = "rpds_py-0.12.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47aeceb4363851d17f63069318ba5721ae695d9da55d599b4d6fb31508595278"}, + {file = "rpds_py-0.12.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0525847f83f506aa1e28eb2057b696fe38217e12931c8b1b02198cfe6975e142"}, + {file = "rpds_py-0.12.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efbe0b5e0fd078ed7b005faa0170da4f72666360f66f0bb2d7f73526ecfd99f9"}, + {file = "rpds_py-0.12.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0fadfdda275c838cba5102c7f90a20f2abd7727bf8f4a2b654a5b617529c5c18"}, + {file = "rpds_py-0.12.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:56dd500411d03c5e9927a1eb55621e906837a83b02350a9dc401247d0353717c"}, + {file = "rpds_py-0.12.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:6915fc9fa6b3ec3569566832e1bb03bd801c12cea030200e68663b9a87974e76"}, + {file = "rpds_py-0.12.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:5f1519b080d8ce0a814f17ad9fb49fb3a1d4d7ce5891f5c85fc38631ca3a8dc4"}, + {file = "rpds_py-0.12.0.tar.gz", hash = "sha256:7036316cc26b93e401cedd781a579be606dad174829e6ad9e9c5a0da6e036f80"}, ] [[package]] name = "ruamel-yaml" -version = "0.18.2" +version = "0.18.5" description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" optional = false -python-versions = ">=3" +python-versions = ">=3.7" files = [ - {file = "ruamel.yaml-0.18.2-py3-none-any.whl", hash = "sha256:92076ac8a83dbf44ca661dbed3c935229c8cbc2f10b05959dd3bd5292d8353d3"}, - {file = "ruamel.yaml-0.18.2.tar.gz", hash = "sha256:9bce33f7a814cea4c29a9c62fe872d2363d6220b767891d956eacea8fa5e6fe8"}, + {file = "ruamel.yaml-0.18.5-py3-none-any.whl", hash = "sha256:a013ac02f99a69cdd6277d9664689eb1acba07069f912823177c5eced21a6ada"}, + {file = "ruamel.yaml-0.18.5.tar.gz", hash = "sha256:61917e3a35a569c1133a8f772e1226961bf5a1198bea7e23f06a0841dea1ab0e"}, ] [package.dependencies] @@ -3362,44 +3533,6 @@ nativelib = ["pyobjc-framework-Cocoa", "pywin32"] objc = ["pyobjc-framework-Cocoa"] win32 = ["pywin32"] -[[package]] -name = "setuptools" -version = "68.2.2" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, - {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - -[[package]] -name = "setuptools-scm" -version = "8.0.4" -description = "the blessed package to manage your versions by scm tags" -optional = false -python-versions = ">=3.8" -files = [ - {file = "setuptools-scm-8.0.4.tar.gz", hash = "sha256:b5f43ff6800669595193fd09891564ee9d1d7dcb196cab4b2506d53a2e1c95c7"}, - {file = "setuptools_scm-8.0.4-py3-none-any.whl", hash = "sha256:b47844cd2a84b83b3187a5782c71128c28b4c94cad8bfb871da2784a5cb54c4f"}, -] - -[package.dependencies] -packaging = ">=20" -setuptools = "*" -tomli = {version = ">=1", markers = "python_version < \"3.11\""} -typing-extensions = "*" - -[package.extras] -docs = ["entangled-cli[rich]", "mkdocs", "mkdocs-entangled-plugin", "mkdocs-material", "mkdocstrings[python]", "pygments"] -rich = ["rich"] -test = ["build", "pytest", "rich", "wheel"] - [[package]] name = "shapely" version = "2.0.2" @@ -3537,22 +3670,22 @@ test = ["cython (>=3.0)", "filelock", "html5lib", "pytest (>=4.6)", "setuptools [[package]] name = "sphinx-autodoc-typehints" -version = "1.24.0" +version = "1.24.1" description = "Type hints (PEP 484) support for the Sphinx autodoc extension" optional = false python-versions = ">=3.8" files = [ - {file = "sphinx_autodoc_typehints-1.24.0-py3-none-any.whl", hash = "sha256:6a73c0c61a9144ce2ed5ef2bed99d615254e5005c1cc32002017d72d69fb70e6"}, - {file = "sphinx_autodoc_typehints-1.24.0.tar.gz", hash = "sha256:94e440066941bb237704bb880785e2d05e8ae5406c88674feefbb938ad0dc6af"}, + {file = "sphinx_autodoc_typehints-1.24.1-py3-none-any.whl", hash = "sha256:4cc16c5545f2bf896ca52a854babefe3d8baeaaa033d13a7f179ac1d9feb02d5"}, + {file = "sphinx_autodoc_typehints-1.24.1.tar.gz", hash = "sha256:06683a2b76c3c7b1931b75e40e0211866fbb50ba4c4e802d0901d9b4e849add2"}, ] [package.dependencies] -sphinx = ">=7.0.1" +sphinx = ">=7.1.2" [package.extras] -docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)"] +docs = ["furo (>=2023.7.26)", "sphinx (>=7.1.2)"] numpy = ["nptyping (>=2.5)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "sphobjinv (>=2.3.1)", "typing-extensions (>=4.6.3)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3)", "diff-cover (>=7.7)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "sphobjinv (>=2.3.1)", "typing-extensions (>=4.7.1)"] [[package]] name = "sphinx-jinja2-compat" @@ -3916,13 +4049,13 @@ files = [ [[package]] name = "traitlets" -version = "5.12.0" +version = "5.13.0" description = "Traitlets Python configuration system" optional = false python-versions = ">=3.8" files = [ - {file = "traitlets-5.12.0-py3-none-any.whl", hash = "sha256:81539f07f7aebcde2e4b5ab76727f53eabf18ad155c6ed7979a681411602fa47"}, - {file = "traitlets-5.12.0.tar.gz", hash = "sha256:833273bf645d8ce31dcb613c56999e2e055b1ffe6d09168a164bcd91c36d5d35"}, + {file = "traitlets-5.13.0-py3-none-any.whl", hash = "sha256:baf991e61542da48fe8aef8b779a9ea0aa38d8a54166ee250d5af5ecf4486619"}, + {file = "traitlets-5.13.0.tar.gz", hash = "sha256:9b232b9430c8f57288c1024b34a8f0251ddcc47268927367a0dd3eeaca40deb5"}, ] [package.extras] @@ -3951,17 +4084,6 @@ files = [ {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, ] -[[package]] -name = "tzdata" -version = "2023.3" -description = "Provider of IANA time zone data" -optional = false -python-versions = ">=2" -files = [ - {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"}, - {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"}, -] - [[package]] name = "uri-template" version = "1.3.0" @@ -3995,13 +4117,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "wcwidth" -version = "0.2.8" +version = "0.2.9" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = "*" files = [ - {file = "wcwidth-0.2.8-py2.py3-none-any.whl", hash = "sha256:77f719e01648ed600dfa5402c347481c0992263b81a027344f3e1ba25493a704"}, - {file = "wcwidth-0.2.8.tar.gz", hash = "sha256:8705c569999ffbb4f6a87c6d1b80f324bd6db952f5eb0b95bc07517f4c1813d4"}, + {file = "wcwidth-0.2.9-py2.py3-none-any.whl", hash = "sha256:9a929bd8380f6cd9571a968a9c8f4353ca58d7cd812a4822bba831f8d685b223"}, + {file = "wcwidth-0.2.9.tar.gz", hash = "sha256:a675d1a4a2d24ef67096a04b85b02deeecd8e226f57b5e3a72dbb9ed99d27da8"}, ] [[package]] @@ -4100,4 +4222,4 @@ docs = ["Sphinx (<4.0)", "autoclasstoc", "myst-parser", "pydocstyle", "sphinx-rt [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.12" -content-hash = "469f4ac96613eaaeae66e5bd5d33dd34ade742ad9b9e7c99ae67135f21244423" +content-hash = "f56080a0f708268b249a138bc0d5c41ab3a196f19a13eb8dbd21e06c4b4def43" diff --git a/pyproject.toml b/pyproject.toml index 03858f4..2c61760 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,11 +9,12 @@ readme = "README.md" python = ">=3.10,<3.12" xtgeo = "^3.4.0" numpy = "^1.26.0" -pandas = "^2.1.1" +pandas = "<2.0" scipy = "^1.11.3" progress = "^1.6" urllib3 = "^2.0.6" meshio = {extras = ["all"], version = "^5.3.4"} +resqpy = "^4.12.1" [tool.poetry.group.dev.dependencies] ipykernel = "^6.25.2" From ed0e51e2b9228845d9f7790e1f6b04f384d326d7 Mon Sep 17 00:00:00 2001 From: Adam Cheng <52572642+adamchengtkc@users.noreply.github.com> Date: Mon, 6 Nov 2023 12:46:53 +0000 Subject: [PATCH 12/21] DOC: dev container --- .devcontainer/Dockerfile | 6 ++++-- .devcontainer/devcontainer.json | 4 ++-- .devcontainer/postCreate.sh | 2 ++ 3 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 .devcontainer/postCreate.sh diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 86799ef..790ed16 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM dolfinx/dolfinx:stable +FROM dolfinx/dolfinx:v0.7.0 # FROM mcr.microsoft.com/devcontainers/base:jammy ARG DEBIAN_FRONTEND=noninteractive # RUN useradd -ms /bin/bash vscode @@ -28,9 +28,11 @@ RUN DEBIAN_FRONTEND=noninteractive \ # Python and poetry installation USER $USER ARG HOME="/home/$USER" + +# Don't change. Dolfinx in the base image is only compiled with 3.10 ARG PYTHON_VERSION=3.10 -# ARG PYTHON_VERSION=3.10 +# Use poetry pyenv ENV PYENV_ROOT="${HOME}/.pyenv" ENV PATH="${PYENV_ROOT}/shims:${PYENV_ROOT}/bin:${HOME}/.local/bin:$PATH" diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8da29d0..f51ae42 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -11,12 +11,12 @@ // "forwardPorts": [], // 👇 Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": ["poetry install --with dev --no-interaction","poetry run pip install petsc4py==3.20.0 mpi4py==3.1.5 https://github.com/FEniCS/ufl/archive/refs/tags/2023.2.0.zip https://github.com/FEniCS/ffcx/archive/v0.7.0.zip https://github.com/FEniCS/basix/archive/v0.7.0.zip "], + "postCreateCommand": "bash .devcontainer/postCreate.sh", // 👇 Configure tool-specific properties. "customizations": { "vscode": { - "extensions":["ms-python.python", "njpwerner.autodocstring"] + "extensions":["ms-python.python", "njpwerner.autodocstring","ms-python.autopep8"] } }, "features": { diff --git a/.devcontainer/postCreate.sh b/.devcontainer/postCreate.sh new file mode 100644 index 0000000..af415a5 --- /dev/null +++ b/.devcontainer/postCreate.sh @@ -0,0 +1,2 @@ +poetry install --with dev --no-interaction +poetry run pip install petsc4py==3.20.0 mpi4py==3.1.5 https://github.com/FEniCS/ufl/archive/refs/tags/2023.2.0.zip https://github.com/FEniCS/ffcx/archive/v0.7.0.zip https://github.com/FEniCS/basix/archive/v0.7.0.zip \ No newline at end of file From 82d7661ec3f578ebaf03bb37055a2ba2e791b10a Mon Sep 17 00:00:00 2001 From: Adam Cheng <52572642+adamchengtkc@users.noreply.github.com> Date: Mon, 6 Nov 2023 12:47:13 +0000 Subject: [PATCH 13/21] DOC: grid example --- docs/notebooks/3D_simulation.ipynb | 337 ++++++++++++++++++ docs/notebooks/Build_with_surface_grids.ipynb | 267 ++++++++++++++ 2 files changed, 604 insertions(+) create mode 100644 docs/notebooks/3D_simulation.ipynb create mode 100644 docs/notebooks/Build_with_surface_grids.ipynb diff --git a/docs/notebooks/3D_simulation.ipynb b/docs/notebooks/3D_simulation.ipynb new file mode 100644 index 0000000..55bd711 --- /dev/null +++ b/docs/notebooks/3D_simulation.ipynb @@ -0,0 +1,337 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import warmth\n", + "from pathlib import Path" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "maps_dir = Path(\"./data/\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "model = warmth.Model()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "inputs = model.builder.input_horizons_template" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "

\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
AgeFile_nameFacies_mapsStratigraphy
000.griNoneOnlap
16666.griNoneOnlap
2100100.griNoneOnlap
3163163.griNoneErosive
4168168.griNoneErosive
5170170.griNoneOnlap
6182182.griNoneErosive
\n", + "
" + ], + "text/plain": [ + " Age File_name Facies_maps Stratigraphy\n", + "0 0 0.gri None Onlap\n", + "1 66 66.gri None Onlap\n", + "2 100 100.gri None Onlap\n", + "3 163 163.gri None Erosive\n", + "4 168 168.gri None Erosive\n", + "5 170 170.gri None Onlap\n", + "6 182 182.gri None Erosive" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#Add surface grids to the table. You can use other method as well\n", + "inputs.loc[0]=[0,\"0.gri\",None,\"Onlap\"]\n", + "inputs.loc[1]=[66,\"66.gri\",None,\"Onlap\"]\n", + "inputs.loc[2]=[100,\"100.gri\",None,\"Onlap\"]\n", + "inputs.loc[3]=[163,\"163.gri\",None,\"Erosive\"]\n", + "inputs.loc[4]=[168,\"168.gri\",None,\"Erosive\"]\n", + "inputs.loc[5]=[170,\"170.gri\",None,\"Onlap\"]\n", + "inputs.loc[6]=[182,\"182.gri\",None,\"Erosive\"]\n", + "model.builder.input_horizons=inputs\n", + "inputs" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "inc = 2000\n", + "model.builder.define_geometry(maps_dir/\"0.gri\",xinc=inc,yinc=inc,fformat=\"irap_binary\")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "model.builder.extract_nodes(4,maps_dir)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "182" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model.parameters.time_start" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "from warmth.data import haq87\n", + "model.builder.set_eustatic_sea_level(haq87)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "for i in model.builder.iter_node():\n", + " i.rift=[[182,175]]" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "model.simulator.simulate_every = 1" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "102" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model.builder.n_valid_node" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "model.simulator.simulate_every=1" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Processing... |################################| 102/102\n", + "\u001b[?25h" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 471 ms, sys: 224 ms, total: 696 ms\n", + "Wall time: 31.6 s\n" + ] + } + ], + "source": [ + "%%time\n", + "model.simulator.run(save=True,purge=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Rebuild/reload mesh at tti= 182\n", + "Using 1D Node parameters NodeParameters1D(shf=0.03, hc=30000.0, hw=3600.0, hLith=130000.0, kLith=3.109, kCrust=2.5, kAsth=100, rhp=2, crustliquid=2500.0, crustsolid=2800.0, lithliquid=2700.0, lithsolid=3300.0, asthliquid=2700.0, asthsolid=3200.0, T0=5, Tm=1330.0, qbase=0.03)\n", + "builing\n", + "saving\n" + ] + }, + { + "ename": "", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[1;31mThe Kernel crashed while executing code in the the current cell or a previous cell. Please review the code in the cell(s) to identify a possible cause of the failure. Click here for more info. View Jupyter log for further details." + ] + } + ], + "source": [ + "from warmth.mesh_model import run\n", + "run(model,start_time=model.parameters.time_start,end_time=0)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.13" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/notebooks/Build_with_surface_grids.ipynb b/docs/notebooks/Build_with_surface_grids.ipynb new file mode 100644 index 0000000..95271b7 --- /dev/null +++ b/docs/notebooks/Build_with_surface_grids.ipynb @@ -0,0 +1,267 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Build multi-1D models with maps" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "import os\n", + "import multiprocessing\n", + "from pathlib import Path\n", + "import sys\n", + "\n", + "sys.path.append(\"../\")\n", + "import numpy as np\n", + "import warmth" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "n_cpu = multiprocessing.cpu_count()\n", + "print(\"Available CPUs\",n_cpu)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "os.getcwd()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "maps_dir = Path(\"../data/mapA\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Instantiate a model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model = warmth.Model()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "inputs = model.builder.input_horizons_template" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#Add surface grids to the table. You can use other method as well\n", + "inputs.loc[0]=[0,\"0.gri\",None,\"Onlap\"]\n", + "inputs.loc[1]=[66,\"66.gri\",None,\"Onlap\"]\n", + "inputs.loc[2]=[100,\"100.gri\",None,\"Onlap\"]\n", + "inputs.loc[3]=[163,\"163.gri\",None,\"Erosive\"]\n", + "inputs.loc[4]=[168,\"168.gri\",None,\"Erosive\"]\n", + "inputs.loc[5]=[170,\"170.gri\",None,\"Onlap\"]\n", + "inputs.loc[6]=[182,\"182.gri\",None,\"Erosive\"]\n", + "model.builder.input_horizons=inputs\n", + "inputs" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Make a grid of locations or specify X, Y extraction" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "inc = 2000\n", + "model.builder.define_geometry(maps_dir/\"0.gri\",xinc=inc,yinc=inc,fformat=\"irap_binary\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "model.builder.extract_nodes(4,maps_dir)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model.parameters.time_start" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from warmth.data import haq87\n", + "model.builder.set_eustatic_sea_level(haq87)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for i in model.builder.iter_node():\n", + " i.rift=[[182,175]]\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Create an input data table for horizon and age extraction" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# simulate every 10 nodes\n", + "for index in model.builder.grid.indexing_arr:\n", + " if (index[0] % 10 > 0):\n", + " pass\n", + " else:\n", + " if isinstance(model.builder.nodes[index[0]][index[1]],bool) is False:\n", + " model.builder.nodes[index[0]][index[1]] = False\n", + " if (index[1] % 10 > 0):\n", + " pass\n", + " else:\n", + " if isinstance(model.builder.nodes[index[0]][index[1]],bool) is False:\n", + " model.builder.nodes[index[0]][index[1]] = False\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "indexer_full_sim = [i.indexer for i in model.builder.iter_node() if i._full_simulation is True]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model.builder.nodes[1][1]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "model.simulator.run(save=False,purge=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model.builder.nodes[0][1]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for i in model.builder.iter_node():\n", + " if i is not False:\n", + " print(i.result.heatflow(0))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "file_extension": ".py", + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.6" + }, + "mimetype": "text/x-python", + "name": "python", + "npconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": 3 + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 4acc591ae5f9a152e71da12e422c10f4d5478ebc Mon Sep 17 00:00:00 2001 From: Adam Cheng <52572642+adamchengtkc@users.noreply.github.com> Date: Mon, 6 Nov 2023 12:47:56 +0000 Subject: [PATCH 14/21] CLN: docs --- .../notebooks/Build_with_surface_grids.ipynb_ | 267 ------------------ docs/notebooks/data/0.gri | 4 +- docs/notebooks/data/100.gri | 4 +- docs/notebooks/data/163.gri | 4 +- docs/notebooks/data/168.gri | 4 +- docs/notebooks/data/170.gri | 4 +- docs/notebooks/data/182.gri | 4 +- docs/notebooks/data/66.gri | 4 +- 8 files changed, 14 insertions(+), 281 deletions(-) delete mode 100644 docs/notebooks/Build_with_surface_grids.ipynb_ diff --git a/docs/notebooks/Build_with_surface_grids.ipynb_ b/docs/notebooks/Build_with_surface_grids.ipynb_ deleted file mode 100644 index 95271b7..0000000 --- a/docs/notebooks/Build_with_surface_grids.ipynb_ +++ /dev/null @@ -1,267 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Build multi-1D models with maps" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "import os\n", - "import multiprocessing\n", - "from pathlib import Path\n", - "import sys\n", - "\n", - "sys.path.append(\"../\")\n", - "import numpy as np\n", - "import warmth" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "n_cpu = multiprocessing.cpu_count()\n", - "print(\"Available CPUs\",n_cpu)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "os.getcwd()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "maps_dir = Path(\"../data/mapA\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Instantiate a model" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model = warmth.Model()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "inputs = model.builder.input_horizons_template" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#Add surface grids to the table. You can use other method as well\n", - "inputs.loc[0]=[0,\"0.gri\",None,\"Onlap\"]\n", - "inputs.loc[1]=[66,\"66.gri\",None,\"Onlap\"]\n", - "inputs.loc[2]=[100,\"100.gri\",None,\"Onlap\"]\n", - "inputs.loc[3]=[163,\"163.gri\",None,\"Erosive\"]\n", - "inputs.loc[4]=[168,\"168.gri\",None,\"Erosive\"]\n", - "inputs.loc[5]=[170,\"170.gri\",None,\"Onlap\"]\n", - "inputs.loc[6]=[182,\"182.gri\",None,\"Erosive\"]\n", - "model.builder.input_horizons=inputs\n", - "inputs" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Make a grid of locations or specify X, Y extraction" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "inc = 2000\n", - "model.builder.define_geometry(maps_dir/\"0.gri\",xinc=inc,yinc=inc,fformat=\"irap_binary\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%time\n", - "model.builder.extract_nodes(4,maps_dir)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model.parameters.time_start" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from warmth.data import haq87\n", - "model.builder.set_eustatic_sea_level(haq87)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for i in model.builder.iter_node():\n", - " i.rift=[[182,175]]\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Create an input data table for horizon and age extraction" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# simulate every 10 nodes\n", - "for index in model.builder.grid.indexing_arr:\n", - " if (index[0] % 10 > 0):\n", - " pass\n", - " else:\n", - " if isinstance(model.builder.nodes[index[0]][index[1]],bool) is False:\n", - " model.builder.nodes[index[0]][index[1]] = False\n", - " if (index[1] % 10 > 0):\n", - " pass\n", - " else:\n", - " if isinstance(model.builder.nodes[index[0]][index[1]],bool) is False:\n", - " model.builder.nodes[index[0]][index[1]] = False\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "indexer_full_sim = [i.indexer for i in model.builder.iter_node() if i._full_simulation is True]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model.builder.nodes[1][1]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%time\n", - "model.simulator.run(save=False,purge=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "model.builder.nodes[0][1]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for i in model.builder.iter_node():\n", - " if i is not False:\n", - " print(i.result.heatflow(0))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "file_extension": ".py", - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.6" - }, - "mimetype": "text/x-python", - "name": "python", - "npconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": 3 - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/docs/notebooks/data/0.gri b/docs/notebooks/data/0.gri index 1af556c..8e3decd 100644 --- a/docs/notebooks/data/0.gri +++ b/docs/notebooks/data/0.gri @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:67506fbe1db74315561e3e251690b747ee208adf606f20339feefd98a2dbe7ce -size 222628 +oid sha256:45ae0d96a78f558d651e31d256a5528b433911977fc8051081747db0e904b16c +size 1067604 diff --git a/docs/notebooks/data/100.gri b/docs/notebooks/data/100.gri index 2920602..241c38d 100644 --- a/docs/notebooks/data/100.gri +++ b/docs/notebooks/data/100.gri @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:977a6abc8956060dfe410163383070af9d0b3265b0e378ecffa452369fbe692a -size 222628 +oid sha256:48847b5895e31516fb0a701f08d2609b43020bdfb55dcbe523866aec5051f487 +size 1067604 diff --git a/docs/notebooks/data/163.gri b/docs/notebooks/data/163.gri index f870ea8..9e009c7 100644 --- a/docs/notebooks/data/163.gri +++ b/docs/notebooks/data/163.gri @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fe26b4139eb3ddfd40671f21325951ac55370d1bf17ea43a655c567c04c370d4 -size 222628 +oid sha256:849d032dbf1683f99793647e270b9e667019acc1bf716bdea1ce580006461613 +size 1067604 diff --git a/docs/notebooks/data/168.gri b/docs/notebooks/data/168.gri index 4decfb9..c682d07 100644 --- a/docs/notebooks/data/168.gri +++ b/docs/notebooks/data/168.gri @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3bb9768864f99c86df978818b605fe6f1165781557392458a50b35b91c0654da -size 222628 +oid sha256:d3430e8e8a338a3c8ddc7576d68906505e3ab506f1afd9da96b23a5eb647c93b +size 1067604 diff --git a/docs/notebooks/data/170.gri b/docs/notebooks/data/170.gri index 113790d..c501ffc 100644 --- a/docs/notebooks/data/170.gri +++ b/docs/notebooks/data/170.gri @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3b509f2b4ad888816510be1402abf1de777037ae70dbb68eacc4a666aaebeb3c -size 222628 +oid sha256:d2e2d0411f4d36f1acf2b7a06d397c67d8f345de86ecda0564d68f7dde747a00 +size 1067604 diff --git a/docs/notebooks/data/182.gri b/docs/notebooks/data/182.gri index 0925c2a..35b65ac 100644 --- a/docs/notebooks/data/182.gri +++ b/docs/notebooks/data/182.gri @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ffafa4ddcbfec39760f314cf11cd7841826fefe7585147346ae24bb8c90adea5 -size 222628 +oid sha256:8da03388145cd2c58e6f04322a8e7163a8b22134b1b4f7bafd64b1c86a379b1f +size 1067604 diff --git a/docs/notebooks/data/66.gri b/docs/notebooks/data/66.gri index 72a48c1..98fbcf4 100644 --- a/docs/notebooks/data/66.gri +++ b/docs/notebooks/data/66.gri @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:743aff905b9abba575555cf70a1b3c2a73af7c13abfa0b710a5149ca88e6e29b -size 222628 +oid sha256:29df4290186312f55b633755802437e9ff5249774d07227d17636cebddd41d42 +size 1067604 From ec975788fb9624300324bbd86ddddadb8cdcc0a3 Mon Sep 17 00:00:00 2001 From: Adam Cheng <52572642+adamchengtkc@users.noreply.github.com> Date: Mon, 6 Nov 2023 12:48:34 +0000 Subject: [PATCH 15/21] CLN: refactor 3D code --- warmth/build.py | 101 ++- warmth/mesh_model.py | 1375 ++++++++++++++++++++++++++++++++++++++ warmth/mesh_utils.py | 110 +++ warmth/parameters.py | 2 +- warmth/postprocessing.py | 102 +++ warmth/resqpy_helpers.py | 460 +++++++++++++ warmth/simulator.py | 65 +- warmth/utils.py | 25 +- 8 files changed, 2187 insertions(+), 53 deletions(-) create mode 100644 warmth/mesh_model.py create mode 100644 warmth/mesh_utils.py create mode 100644 warmth/resqpy_helpers.py diff --git a/warmth/build.py b/warmth/build.py index 41f4306..10157ab 100644 --- a/warmth/build.py +++ b/warmth/build.py @@ -105,6 +105,9 @@ def __init__(self): self.temperature_out:np.ndarray[np.float64]|None=None self._idsed:np.ndarray[np.int32]|None=None self._ht:float = self.hLith+self.hc+150e3 + self._crust_ls:np.ndarray[np.float64]|None=None + self._lith_ls:np.ndarray[np.float64]|None=None + self._subsidence:np.ndarray[np.float64]|None=None @property @@ -121,6 +124,51 @@ def result(self)-> Results|None: return None else: return Results(self._depth_out,self.temperature_out,self._idsed,self.sediments,self.kCrust,self.kLith,self.kAsth) + @property + def crust_ls(self)->np.ndarray[np.float64]: + if isinstance(self.result,Results): + all_age = self.result.ages + val = np.zeros(all_age.size) + for age in all_age: + val[age] = self.result.crust_thickness(age) + + return val + else: + return self._crust_ls + @property + def lith_ls(self)->np.ndarray[np.float64]: + if isinstance(self.result,Results): + all_age = self.result.ages + val = np.zeros(all_age.size) + for age in all_age: + val[age] = self.result.lithosphere_thickness(age) + return val + else: + return self._lith_ls + @property + def subsidence(self)->np.ndarray[np.float64]: + if isinstance(self.result,Results): + all_age = self.result.ages + val = np.zeros(all_age.size) + for age in all_age: + val[age] = self.result.seabed(age) + return val + else: + return self._subsidence + @property + def sed_thickness_ls(self)->float: + if isinstance(self.result,Results): + all_age = self.result.ages + val = np.zeros(all_age.size) + for age in all_age: + seabed = self.result.seabed(age) + top_crust = self.result.top_crust(age) + val[age] = top_crust - seabed + return val + else: + return self.sed[-1,1,:] - self.sed[0,0,:] + + @property def _name(self) -> str: return str(self.X).replace(".", "_")+"__"+str(self.Y).replace(".", "_") @@ -255,7 +303,12 @@ def __init__(self, origin_x: float, origin_y: float, num_nodes_x: int, num_nodes self.__location_xtgeo = None self.__location_xtgeo_z = None self._indexing_arr = None - + @property + def xmax(self)->float: + return self.origin_x+ (self.num_nodes_x*self.step_x) + @property + def ymax(self)->float: + return self.origin_y+ (self.num_nodes_y*self.step_y) @property def location_grid(self)->np.ndarray: """Locations of all 1D nodes @@ -346,33 +399,7 @@ def dump(self, filepath: Path): pickle.dump(self, f, protocol=pickle.HIGHEST_PROTOCOL) return -def interpolateNode(interpolationNodes: List[single_node], interpolationWeights=None) -> single_node: - assert len(interpolationNodes)>0 - if interpolationWeights is None: - interpolationWeights = np.ones([len(interpolationNodes),1]) - assert len(interpolationNodes)==len(interpolationWeights) - wsum = np.sum(np.array(interpolationWeights)) - iWeightNorm = [ w/wsum for w in interpolationWeights] - - node = single_node() - node.__dict__.update(interpolationNodes[0].__dict__) - node.X = np.sum( np.array( [node.X * w for node,w in zip(interpolationNodes,iWeightNorm)] ) ) - node.Y = np.sum( np.array( [node.Y * w for node,w in zip(interpolationNodes,iWeightNorm)] ) ) - - times = range(node.result._depth.shape[1]) - node.subsidence = np.sum( np.array( [ [node.result.seabed(t) for t in times] * w for node,w in zip(interpolationNodes,iWeightNorm)] ) , axis = 0) - node.crust_ls = np.sum( np.array( [ [node.result.crust_thickness(t) for t in times] * w for node,w in zip(interpolationNodes,iWeightNorm)] ) , axis = 0) - node.lith_ls = np.sum( np.array( [ [node.result.lithosphere_thickness(t) for t in times] * w for node,w in zip(interpolationNodes,iWeightNorm)] ) , axis = 0) - - node.beta = np.sum( np.array( [node.beta * w for node,w in zip(interpolationNodes,iWeightNorm)] ) , axis = 0) - node.kAsth = np.sum( np.array( [node.kAsth * w for node,w in zip(interpolationNodes,iWeightNorm)] ) , axis = 0) - node.kLith = np.sum( np.array( [node.kLith * w for node,w in zip(interpolationNodes,iWeightNorm)] ) , axis = 0) - node.depth_out = np.sum([node.result._depth*w for n,w in zip(interpolationNodes[0:1], [1] )], axis=0) - node.temperature_out = np.sum([n.result._temperature*w for n,w in zip(interpolationNodes[0:1], [1] )], axis=0) - - node.sed = np.sum([n.sed*w for n,w in zip(interpolationNodes,iWeightNorm)], axis=0) - node.sed_thickness_ls = node.sed[-1,1,:] - node.sed[0,0,:] - return node + class Builder: def __init__(self, parameters: Parameters): @@ -390,7 +417,7 @@ def __init__(self, parameters: Parameters): self._ymax = 0 self.boundary = None self.grid: Grid | None = None - self.nodes: list = [] + self.nodes: list[single_node] = [] @property def single_node_sediments_inputs_template(self): @@ -467,7 +494,6 @@ def _extract_single_horizon(self, location = self.grid._location_xtgeo_z loc_depth_val = top.get_fence(location) loc_depth_val = loc_depth_val.filled(np.nan) - if ( isinstance(input_data_row["Facies_maps"], str) and (input_data_row["Facies_maps"]) != "faci_m//-1.pmd" @@ -752,13 +778,15 @@ def define_geometry(self, path: Path, xinc: float = None, yinc: float = None, ff Map format supported by xtgeo, by default "irap_binary" """ hor = xtgeo.surface_from_file(path, values=False, fformat=fformat) + hor.unrotate() + hor.autocrop() + if hor.yflip != 1: + raise Exception("Flipped surface not supported") if isinstance(xinc, type(None)): xinc = hor.xinc if isinstance(yinc, type(None)): yinc = hor.yinc - nx = math.ceil((hor.xmax+xinc-hor.xmin)/xinc) - ny = math.ceil((hor.ymax+yinc-hor.ymin)/yinc) - self.grid = Grid(hor.xmin, hor.ymin, nx, ny, xinc, yinc) + self.grid = Grid(hor.xori, hor.yori, hor.ncol, hor.nrow, xinc, yinc) self.nodes = self.grid.make_grid_arr() return @@ -785,7 +813,12 @@ def iter_node(self)->Iterator[single_node]: for col in row: if isinstance(col,bool)==False: yield col - + @property + def indexer_full_sim(self)->list: + return [i.indexer for i in self.iter_node() if i._full_simulation is True] + @property + def n_valid_node(self)->int: + return len([i for i in self.iter_node()]) def set_eustatic_sea_level(self, sealevel:dict|None=None): """Set eustatic sea level correction for subsidence modelling diff --git a/warmth/mesh_model.py b/warmth/mesh_model.py new file mode 100644 index 0000000..a04ceae --- /dev/null +++ b/warmth/mesh_model.py @@ -0,0 +1,1375 @@ +import numpy as np +from mpi4py import MPI +import meshio +import dolfinx +from petsc4py import PETSc +import ufl +from scipy.interpolate import LinearNDInterpolator + +from warmth.build import single_node +from .model import Model +from warmth.logging import logger +from .mesh_utils import NodeParameters1D, top_crust,top_sed,thick_crust, top_lith, top_asth, top_sed_id, bottom_sed_id,NodeGrid +from .resqpy_helpers import write_tetra_grid_with_properties, write_hexa_grid_with_properties,read_mesh_resqml +def tic(): + #Homemade version of matlab tic and toc functions + import time + global startTime_for_tictoc + startTime_for_tictoc = time.time() + +def toc(msg=""): + import time + if 'startTime_for_tictoc' in globals(): + delta = time.time() - startTime_for_tictoc + print (msg+": Elapsed time is " + str(delta) + " seconds.") + return delta + else: + print ("Toc: start time not set") +class UniformNodeGridFixedSizeMeshModel: + """Manages a 3D heat equation computation using dolfinx + Input is a uniform x-, y-grid of Nodes solved by 1D-SubsHeat + The mesh vertex and cell order stays the same across the simulation + Zero-sized cells will be increased to be of a small minimum size + + The constructor takes a NodeGrid class and the list of 1D nodes + """ + point_domain_edge_map = {} + point_top_vertex_map = {} + point_bottom_vertex_map = {} + def __init__(self, model:Model, modelName="test", sedimentsOnly = False): + self.node1D = [n for n in model.builder.iter_node()] + self.num_nodes = len(self.node1D) + self.mesh = None + + self.modelName = modelName + self.Temp0 = 5 + self.TempBase = 1369 + self.verbose = True + self.minimumCellThick = 0.05 + + self.runSedimentsOnly = sedimentsOnly + + self.numElemInCrust = 0 if self.runSedimentsOnly else 4 # split crust hexahedron into pieces + self.numElemInLith = 0 if self.runSedimentsOnly else 2 # split lith hexahedron into pieces + self.numElemInAsth = 0 if self.runSedimentsOnly else 2 # split asth hexahedron into pieces + + + self.num_nodes_x = model.builder.grid.num_nodes_x + self.num_nodes_y = model.builder.grid.num_nodes_y + self.convexHullEdges = [] + for i in range(self.num_nodes_x-1): + edge = [i, i+1] + self.convexHullEdges.append(edge) + edge = [i+(self.num_nodes_y-1*self.num_nodes_x), i+1+(self.num_nodes_y-1*self.num_nodes_x)] + self.convexHullEdges.append(edge) + for i in range(self.num_nodes_y-1): + edge = [i*self.num_nodes_x, (i+1)*self.num_nodes_x] + self.convexHullEdges.append(edge) + edge = [i*self.num_nodes_x + (self.num_nodes_x-1), (i+1)*self.num_nodes_x+ (self.num_nodes_x-1)] + self.convexHullEdges.append(edge) + + self.useBaseFlux = False + self.baseFluxMagnitude = 0.06 + + self.mesh0_geometry_x = None + self.CGorder = 1 + + self.layer_id_per_vertex = None + self.thermalCond = None + self.mean_porosity = None + self.c_rho = None + self.numberOfSediments = model.builder.input_horizons.shape[0]-1 #skip basement + # self.globalSediments = self.node1D[0].sediments.copy() + + self.parameters1D = self.node1D[0].parameters if hasattr(self.node1D[0], 'parameters') else NodeParameters1D() + self.interpolators = {} + print("Using 1D Node parameters", self.parameters1D) + + def write_tetra_mesh_resqml( self, out_path): + """Prepares arrays and calls the RESQML output helper function: the lith and aesth are removed, and the remaining + vertices and cells are renumbered; the sediment properties are prepared for output. + + out_path: string: path to write the resqml model to (.epc and .h5 files) + + returns the filename (of the .epc file) that was written + """ + def boundary(x): + return np.full(x.shape[1], True) + entities = dolfinx.mesh.locate_entities(self.mesh, 3, boundary ) + tet = dolfinx.cpp.mesh.entities_to_geometry(self.mesh, 3, entities, False) + p0 = self.mesh.geometry.x[tet,:] + tet_to_keep = [] + p_to_keep = set() + lid_to_keep = [] + cell_id_to_keep = [] + for i,t in enumerate(tet): + ps = p0[i] + minY = np.amin( np.array( [p[1] for p in ps] ) ) + midpoint = np.sum(ps,axis=0)*0.25 + lid0 = self.findLayerID(self.tti, midpoint) + # + # discard aesth and lith (layer IDs -2, -3) + # + if (lid0>=-1) and (lid0<100): + tet_to_keep.append(t) + lid_to_keep.append(lid0) + cell_id_to_keep.append(self.node_index[i]) + if abs(self.node_index[i].Y-minY)>1: + print("unusual Y coordinate:", minY, self.node1D[self.node_index[i]].Y, i, self.node_index[i], self.node1D[self.node_index[i]]) + for ti in t: + p_to_keep.add(ti) + poro0_per_cell = np.array( [ self.getSedimentPropForLayerID('phi', lid,cid) for lid,cid in zip(lid_to_keep,cell_id_to_keep) ] ) + decay_per_cell = np.array( [ self.getSedimentPropForLayerID('decay', lid,cid) for lid,cid in zip(lid_to_keep,cell_id_to_keep) ]) + density_per_cell = np.array( [ self.getSedimentPropForLayerID('solidus', lid,cid) for lid,cid in zip(lid_to_keep,cell_id_to_keep) ]) + cond_per_cell = np.array( [ self.getSedimentPropForLayerID('k_cond', lid,cid) for lid,cid in zip(lid_to_keep,cell_id_to_keep) ]) + rhp_per_cell = np.array( [ self.getSedimentPropForLayerID('rhp', lid,cid) for lid,cid in zip(lid_to_keep,cell_id_to_keep) ]) + + lid_per_cell = np.array(lid_to_keep) + + points_cached = [] + point_original_to_cached = np.ones(self.mesh.geometry.x.shape[0], dtype = np.int32) * (-1) + for i in range(self.mesh.geometry.x.shape[0]): + if (i in p_to_keep): + point_original_to_cached[i] = len(points_cached) + points_cached.append(self.mesh.geometry.x[i,:]) + tet_renumbered = [ [point_original_to_cached[i] for i in tet] for tet in tet_to_keep ] + T_per_vertex = [ self.uh.x.array[i] for i in range(self.mesh.geometry.x.shape[0]) if i in p_to_keep ] + age_per_vertex = [ self.mesh_vertices_age[i] for i in range(self.mesh.geometry.x.shape[0]) if i in p_to_keep ] + + from os import path + filename = path.join(out_path, self.modelName+'_'+str(self.tti)+'.epc') + write_tetra_grid_with_properties(filename, np.array(points_cached), tet_renumbered, "tetramesh", + np.array(T_per_vertex), np.array(age_per_vertex), poro0_per_cell, decay_per_cell, density_per_cell, + cond_per_cell, rhp_per_cell, lid_per_cell) + return filename + + + def write_hexa_mesh_resqml( self, out_path): + """Prepares arrays and calls the RESQML output helper function for hexa meshes: the lith and aesth are removed, and the remaining + vertices and cells are renumbered; the sediment properties are prepared for output. + + out_path: string: path to write the resqml model to (.epc and .h5 files) + + returns the filename (of the .epc file) that was written + """ + x_original_order = self.mesh.geometry.x[:].copy() + reverse_reindex_order = np.arange( self.mesh_vertices.shape[0] ) + for ind,val in enumerate(self.mesh_reindex): + x_original_order[val,:] = self.mesh.geometry.x[ind,:] + reverse_reindex_order[val] = ind + hexaHedra, hex_data_layerID, hex_data_nodeID = self.buildHexahedra() + + hexa_to_keep = [] + p_to_keep = set() + lid_to_keep = [] + cond_per_cell = [] + cell_id_to_keep = [] + for i,h in enumerate(hexaHedra): + lid0 = hex_data_layerID[i] + # + # discard aesth and lith (layer IDs -2, -3) + # + if (lid0>=-1) and (lid0<100): + hexa_to_keep.append(h) + lid_to_keep.append(lid0) + # cell_id_to_keep.append(self.node_index[i]) + cell_id_to_keep.append(hex_data_nodeID[i]) + minY = np.amin(np.array ( [x_original_order[hi,1] for hi in h] )) + if abs( self.node1D[hex_data_nodeID[i]].Y - minY)>1: + print("weird Y:", minY, self.node1D[hex_data_nodeID[i]].Y, abs( self.node1D[hex_data_nodeID[i]].Y - minY), i, hex_data_nodeID[i]) + breakpoint() + # if (minY>40000): + # pp = self.getSedimentPropForLayerID('phi', lid0, hex_data_nodeID[i]) + # if (pp<0.7) and lid0>=0: + # print("weird phi: ", pp, minY, self.node1D[hex_data_nodeID[i]].Y, abs( self.node1D[hex_data_nodeID[i]].Y - minY), i, hex_data_nodeID[i]) + # breakpoint() + k_cond_mean = [] + for hi in h: + p_to_keep.add(hi) + k_cond_mean.append(self.thermalCond.x.array[hi]) + cond_per_cell.append( np.mean(np.array(k_cond_mean))) + + poro0_per_cell = np.array( [ self.getSedimentPropForLayerID('phi', lid,cid) for lid,cid in zip(lid_to_keep,cell_id_to_keep) ] ) + decay_per_cell = np.array( [ self.getSedimentPropForLayerID('decay', lid,cid) for lid,cid in zip(lid_to_keep,cell_id_to_keep) ]) + density_per_cell = np.array( [ self.getSedimentPropForLayerID('solidus', lid,cid) for lid,cid in zip(lid_to_keep,cell_id_to_keep) ]) + cond_per_cell = np.array( [ self.getSedimentPropForLayerID('k_cond', lid,cid) for lid,cid in zip(lid_to_keep,cell_id_to_keep) ]) + rhp_per_cell = np.array( [ self.getSedimentPropForLayerID('rhp', lid,cid) for lid,cid in zip(lid_to_keep,cell_id_to_keep) ]) + lid_per_cell = np.array(lid_to_keep) + + points_cached = [] + point_original_to_cached = np.ones(self.mesh.geometry.x.shape[0], dtype = np.int32) * (-1) + for i in range(self.mesh.geometry.x.shape[0]): + if (i in p_to_keep): + point_original_to_cached[i] = len(points_cached) + points_cached.append(x_original_order[i,:]) + hexa_renumbered = [ [point_original_to_cached[i] for i in hexa] for hexa in hexa_to_keep ] + + for i,h in enumerate(hexa_renumbered): + minY = np.amin(np.array ( [np.array(points_cached)[hi,1] for hi in h] )) + poro0 = poro0_per_cell[i] + lid0 = lid_to_keep[i] + # if (minY>40000) and poro0 < 0.8 and lid0>=0: + # print("problem A", minY, poro0, i, h) + # breakpoint() + # if (minY<40000) and poro0 > 0.8: + # print("problem B", minY, poro0, i, h) + # breakpoint() + + + T_per_vertex = [ self.uh.x.array[reverse_reindex_order[i]] for i in range(self.mesh.geometry.x.shape[0]) if i in p_to_keep ] + age_per_vertex = [ self.mesh_vertices_age[reverse_reindex_order[i]] for i in range(self.mesh.geometry.x.shape[0]) if i in p_to_keep ] + + from os import path + filename_hex = path.join(out_path, self.modelName+'_hexa_'+str(self.tti)+'.epc') + write_hexa_grid_with_properties(filename_hex, np.array(points_cached), hexa_renumbered, "hexamesh", + np.array(T_per_vertex), np.array(age_per_vertex), poro0_per_cell, decay_per_cell, density_per_cell, + cond_per_cell, rhp_per_cell, lid_per_cell) + return filename_hex + + def getSubsidenceAtMultiplePos(self, pos_x, pos_y): + """Returns subsidence values at given list of x,y positions. + TODO: re-design + """ + subs1 = [] + for px,py in zip(pos_x,pos_y): + fkey = self.floatKey2D([px+2e-2, py+2e-2]) + dz = UniformNodeGridFixedSizeMeshModel.point_top_vertex_map.get(fkey, 1e10) + subs1.append(dz) + return np.array(subs1) + + def getBaseAtMultiplePos(self, pos_x, pos_y): + """Returns lowest mesh z values at given list of x,y positions. + TODO: re-design + """ + subs1 = [] + for px,py in zip(pos_x,pos_y): + fkey = self.floatKey2D([px+2e-2, py+2e-2]) + dz = UniformNodeGridFixedSizeMeshModel.point_bottom_vertex_map.get(fkey, 1e10) + subs1.append(dz) + return np.array(subs1) + + def getTopOfLithAtNode(self, tti, node:single_node): + """Returns crust-lith boundary depth at the given time at the given node + """ + z0 = top_lith( node, tti ) if not self.runSedimentsOnly else 0 + return z0 + + def getTopOfAsthAtNode(self, tti, node:single_node): + """Returns crust-lith boundary depth at the given time at the given node + """ + z0 = top_asth( node, tti ) if not self.runSedimentsOnly else 0 + return z0 + + # + def getSedimentPropForLayerID(self, property, layer_id, node_index): + """ + """ + assert property in ['k_cond', 'rhp', 'phi', 'decay', 'solidus', 'liquidus'], "Unknown property " + property + if (layer_id>=0) and (layer_id0.7 and node.Y<40000: + # print("phi", property, phi, node_index, node) + # breakpoint() + # if (property=='phi') and phi<0.7 and node.Y>40000: + # print("phi", property, phi, node_index, node) + # breakpoint() + # phi = self.globalSediments[property][layer_id] + # assert abs(phi-phi0)<1e-6 + return phi + if (layer_id<=-1) and (layer_id>=-3): + lid = -layer_id -1 + if (property=='k_cond'): + return [0.1,0.1,0.1][lid] + if (property=='rhp'): + return [0.0,0.0,0.0][lid] # RHP in crust is not yet supported! + if (property=='phi'): + return [0.1,0.0,0.0][lid] # porosity for crust, lith, aest + if (property=='decay'): + return [0.5,0.5,0.5][lid] # porosity decay for crust, lith, aest + if (property=='solidus'): + return [0.5,0.5,0.5][lid] # solid density decay for crust, lith, aest + if (property=='liquidus'): + return [0.5,0.5,0.5][lid] # solid density decay for crust, lith, aest + return np.nan + + def porosity0ForLayerID(self, layer_id, node_index): + """Porosity (at surface) conductivity value for the given layer index + """ + if (layer_id==-1): + return 0.0,0.0 # porosity (at surface) of crust + if (layer_id==-2): + return 0.0,0.0 # porosity (at surface) of lith + if (layer_id==-3): + return 0.0,0.0 # porosity (at surface) of aesth + if (layer_id>=0) and (layer_id 0 + node = self.node1D[node_index] + phi = node.sediments.phi[layer_id] + decay = node.sediments.decay[layer_id] + return phi, decay + return 0.0, 0.0 + + def cRhoForLayerID(self, ss, node_index): + # + # prefactor 1000 is the heat capacity.. assumed constant + # + if (ss==-1): + return 1000*self.parameters1D.crustsolid + if (ss==-2): + return 1000*self.parameters1D.lithsolid + if (ss==-3): + return 1000*self.parameters1D.crustsolid + if (ss>=0) and (ss=0) and (ss len(self.node1D)-1): + print("cell ID", node_index, len(self.node1D)) + breakpoint() + node = self.node1D[node_index] + kc = node.sediments.k_cond[ss] + return kc + + + def rhpForLayerID(self, ss, node_index): + """Radiogenic heat production for a layer ID index + """ + if (ss==-1): + return 0 + elif (ss==-2): + return 0 + elif (ss==-3): + return 0 + elif (ss>=0) and (ss0) else top_of_sediments + base_crust = mean_top_of_lith + for i in range(1,self.numElemInCrust+1): + self.mesh_vertices_0.append( [ node.X, node.Y, base_of_last_sediments+ (base_crust-base_of_last_sediments)*(i/self.numElemInCrust) ] ) + self.sed_diff_z.append(0.0) + self.mesh_vertices_age_unsorted.append(1000) + + base_lith = mean_top_of_asth + for i in range(1,self.numElemInLith+1): + self.mesh_vertices_0.append( [ node.X, node.Y, base_crust+ (base_lith-base_crust)*(i/self.numElemInLith) ] ) + self.sed_diff_z.append(0.0) + self.mesh_vertices_age_unsorted.append(1000) + + base_aest = 260000 + for i in range(1,self.numElemInAsth+1): + self.mesh_vertices_0.append( [ node.X, node.Y, base_lith+(base_aest-base_lith)*(i/self.numElemInAsth) ] ) + self.sed_diff_z.append(0.0) + self.mesh_vertices_age_unsorted.append(1000) + + assert len(self.mesh_vertices_0) % self.num_nodes ==0 + self.mesh_vertices_0 = np.array(self.mesh_vertices_0) + self.sed_diff_z = np.array(self.sed_diff_z) + self.mesh_vertices = self.mesh_vertices_0.copy() + self.mesh_vertices[:,2] = self.mesh_vertices_0[:,2] + self.sed_diff_z + if (useFakeEncodedZ): + self.mesh_vertices[:,2] = np.ceil(self.mesh_vertices[:,2])*1000 + np.array(list(range(self.mesh_vertices.shape[0])))*0.01 + # zz = self.mesh_vertices[:,2].copy() + # zz2=np.mod(zz,1000) + # # mesh_reindex = (1e-4+zz2*10).astype(np.int32) + + def updateVertices(self): + """Update the mesh vertex positions using the values in self.mesh_vertices, and using the known dolfinx-induded reindexing + """ + self.mesh.geometry.x[:] = self.mesh_vertices[self.mesh_reindex].copy() + self.mesh_vertices_age = np.array(self.mesh_vertices_age_unsorted)[self.mesh_reindex].copy() + self.mesh0_geometry_x = self.mesh.geometry.x.copy() + self.mesh_vertices[:,2] = self.mesh_vertices_0[:,2] - self.sed_diff_z + self.updateTopVertexMap() + if self.runSedimentsOnly: + self.updateBottomVertexMap() + self.mesh_vertices[:,2] = self.mesh_vertices_0[:,2] + self.sed_diff_z + + def buildMesh(self,tti): + """Construct a new mesh at the given time index tti, and determine the vertex re-indexing induced by dolfinx + """ + self.tti = tti + self.buildVertices(time_index=tti, useFakeEncodedZ=True) + self.constructMesh() + print("updatemesh") + self.updateMesh(tti) + # self.buildVertices(time_index=tti, useFakeEncodedZ=False) + # self.updateVertices() + + def updateMesh(self,tti): + """Construct the mesh positions at the given time index tti, and update the existing mesh with the new values + """ + assert self.mesh is not None + self.tti = tti + self.buildVertices(time_index=tti, useFakeEncodedZ=False) + self.updateVertices() + + def buildHexahedra(self): + xpnum = self.num_nodes_x + ypnum = self.num_nodes_y + + nodeQuads = [] + for j in range(ypnum-1): + for i in range(xpnum-1): + i0 = j * (xpnum)+i + q = [ i0, i0+1, i0 + xpnum+1, i0 + xpnum ] + nodeQuads.append(q) + + v_per_n = int(len(self.mesh_vertices) / self.num_nodes) + assert len(self.mesh_vertices) % self.num_nodes ==0 + + hexaHedra = [] + hex_data_layerID = [] + hex_data_nodeID = [] + for q in nodeQuads: + for s in range(v_per_n-1): + h = [] + # + for i in range(4): + i0 = q[i]*v_per_n + s+1 + h.append(i0) + for i in range(4): + h.append(q[i]*v_per_n + s) + hexaHedra.append(h) + lid = s + if (s >= self.numberOfSediments): + ss = s - self.numberOfSediments + # lid = -((s+1)-self.numberOfSediments) + if (ss>=0) and (ss=self.numElemInCrust) and (ss < self.numElemInCrust+self.numElemInLith): + lid = -2 + if (ss>=self.numElemInCrust+self.numElemInLith) and (ss1.0] = 1.0 + res = nz * (self.TempBase-self.Temp0) + self.Temp0 + for i in range(x.shape[1]): + p = x[:,i] + fkey = self.floatKey2D(p+[2e-2, 2e-2,0.0]) + dz = UniformNodeGridFixedSizeMeshModel.point_top_vertex_map.get(fkey, 1e10) + Zmin0 = dz if (dz<1e9) else np.amin(x[2,:]) + nz0 = (p[2] - Zmin0) / (self.averageLABdepth - Zmin0) + nz0 = min(nz0, 1.0) + res[i] = nz0 * (self.TempBase-self.Temp0) + self.Temp0 + if (p[2]>250000): + res[i] = 1369 + # Zmax = np.amax(x[2,:]) + # res[x[2,:]=0): + next_lidval = lidval+1 + while (next_lidval not in self.layer_id_per_vertex[ti]) and (next_lidvalself.numberOfSediments-1): + next_lidval = -1 + if vertex_on_top_of_tet: + self.mesh_vertex_layerIDs[ti] = lidval + elif self.mesh_vertex_layerIDs[ti]==100: + self.mesh_vertex_layerIDs[ti] = next_lidval + else: + if ((lidval) > self.mesh_vertex_layerIDs[ti]) or (self.mesh_vertex_layerIDs[ti]>=100): + self.mesh_vertex_layerIDs[ti] = lidval + self.updateTopVertexMap() + if self.runSedimentsOnly: + self.updateBottomVertexMap() + return thermalCond, c_rho, lid, rhp + + def updateTopVertexMap(self): + """ Updates the point_top_vertex_map, used for fast lookup of subsidence values. + (to be re-designed?) + """ + UniformNodeGridFixedSizeMeshModel.point_top_vertex_map = {} + v_per_n = int(len(self.mesh_vertices) / self.num_nodes) + if not self.runSedimentsOnly: + indices = np.where( np.mod(self.mesh_reindex,v_per_n)==0)[0] + else: + indices = range(self.mesh.geometry.x.shape[0]) + for i in indices: + p = self.mesh.geometry.x[i,:] + fkey = self.floatKey2D(p+[2e-2, 2e-2,0.0]) + dz = UniformNodeGridFixedSizeMeshModel.point_top_vertex_map.get(fkey, 1e10) + if p[2]dz: + UniformNodeGridFixedSizeMeshModel.point_bottom_vertex_map[fkey] = p[2] + + def updateDirichletBaseTemperature(self): + assert False, "to be re-implemented" + + + def buildDirichletBC(self): + """ Generate a dolfinx Dirichlet Boundary condition that applies at the top and bottom vertices. + The values at the edges are those in function self.TemperatureStep + """ + # Dirichlet BC at top and bottom + self.Zmax = np.amax(self.mesh.geometry.x[:,2]) + self.averageLABdepth = np.mean(np.array([ top_sed(n, self.tti) for n in self.node1D])) + def boundary_D_top_bottom(x): + subs0 = self.getSubsidenceAtMultiplePos(x[0,:], x[1,:]) + xx = np.logical_or( np.abs(x[2]-subs0)<5, np.isclose(x[2], self.Zmax) ) + return xx + def boundary_D_top(x): + subs0 = self.getSubsidenceAtMultiplePos(x[0,:], x[1,:]) + # print("subs0", self.tti, subs0.shape, subs0[np.abs(subs0)>1].shape, subs0[np.abs(subs0)>1] ) + #print("subs0", self.tti, subs0.shape, subs0[np.abs(subs0)>1].shape, subs0[np.abs(subs0)>1] ) + xx = np.logical_or( np.abs(x[2]-subs0)<5, np.isclose(x[2], 1e6*self.Zmax) ) + return xx + + if (self.useBaseFlux): + dofs_D = dolfinx.fem.locate_dofs_geometrical(self.V, boundary_D_top) + # print("dofs_D", self.tti, dofs_D.shape) + else: + dofs_D = dolfinx.fem.locate_dofs_geometrical(self.V, boundary_D_top_bottom) + u_bc = dolfinx.fem.Function(self.V) + u_bc.interpolate(self.TemperatureGradient) + print("buildDirichletBC", np.amin(u_bc.x.array), np.amax(u_bc.x.array) ) + # u_bc.interpolate(self.TemperatureStep) + bc = dolfinx.fem.dirichletbc(u_bc, dofs_D) + return bc + + + def resetMesh(self): + self.mesh.geometry.x[:,2] = self.mesh0_geometry_x.copy()[:,2] + + def writeLayerIDFunction(self, outfilename, tti=0): + """ Writes the mesh and the layer ID function (constant value per cell) to the given output file in XDMF format + """ + xdmf = dolfinx.io.XDMFFile(MPI.COMM_WORLD, outfilename, "w") + xdmf.write_mesh(self.mesh) + xdmf.write_function(self.layerIDsFcn, tti) + + def writePoroFunction(self, outfilename, tti=0): + """ Writes the mesh and poro0 function (constant value per cell) to the given output file in XDMF format + """ + xdmf = dolfinx.io.XDMFFile(MPI.COMM_WORLD, outfilename, "w") + xdmf.write_mesh(self.mesh) + xdmf.write_function(self.porosity0, tti) + # xdmf.write_function(self.thermalCond, tti) + + def writeTemperatureFunction(self, outfilename, tti=0): + """ Writes the mesh and the current temperature solution to the given output file in XDMF format + """ + xdmf = dolfinx.io.XDMFFile(MPI.COMM_WORLD, outfilename, "w") + xdmf.write_mesh(self.mesh) + xdmf.write_function(self.u_n, tti) + + def writeOutputFunctions(self, outfilename, tti=0): + """ Writes the mesh, layer IDs, and current temperature solution to the given output file in XDMF format + # + # TODO: this does not work + # + """ + xdmf = dolfinx.io.XDMFFile(MPI.COMM_WORLD, outfilename, "w") + xdmf.write_mesh(self.mesh) + xdmf.write_function(self.layerIDsFcn, tti) + xdmf.write_function(self.u_n, tti) + + def setupSolverAndSolve(self, time_step=-1, no_steps=100, skip_setup = False, initial_state_model = None): + """ Sets up the function spaces, output functions, input function (kappa values), boundary conditions, initial conditions. + Sets up the heat equation in dolfinx, and solves the system in time for the given number of steps. + + Use skip_setup = True to continue a computation (e.g. after deforming the mesh), instead of starting one from scratch + """ + if (not skip_setup): + self.resetMesh() + self.Zmin = np.min(self.mesh_vertices, axis=0)[2] + self.Zmax = np.max(self.mesh_vertices, axis=0)[2] + + # Time-dependent heat problem: + # time-discretized variational form with backwards Euler, + # see: https://fenicsproject.org/pub/tutorial/html/._ftut1006.html + + if (not skip_setup): + # + # define function space + self.FE = ufl.FiniteElement("CG", self.mesh.ufl_cell(), self.CGorder) + self.V = dolfinx.fem.FunctionSpace(self.mesh, self.FE) + + # Define solution variable uh + self.uh = dolfinx.fem.Function(self.V) + self.uh.name = "uh" + + # u_n: solution at previous time step + self.u_n = dolfinx.fem.Function(self.V) + self.u_n.name = "u_n" + + # initialise both with initial condition: either a step function, or the solution from another Model instance + if (initial_state_model is None): + # self.u_n.interpolate(self.TemperatureStep) + self.u_n.interpolate(self.TemperatureGradient) + # self.u_n.interpolate(self.TemperatureFromNode) + else: + self.u_n.x.array[:] = initial_state_model.uh.x.array[:].copy() + self.uh.x.array[:] = self.u_n.x.array[:].copy() + + + self.thermalCond, self.c_rho, self.layerIDsFcn, self.rhpFcn = self.buildKappaAndLayerIDs() + assert not np.any(np.isnan(self.thermalCond.x.array)) + + # self.updateSedimentsConductivity() + self.sedimentsConductivitySekiguchi() + + self.bc = self.buildDirichletBC() + + t=0 + dt = time_step if (time_step>0) else 3600*24*365 * 5000000 + num_steps = no_steps + + # + # solver setup, see: + # https://jorgensd.github.io/dolfinx-tutorial/chapter2/diffusion_code.html + # + + u = ufl.TrialFunction(self.V) + v = ufl.TestFunction(self.V) + + a = self.c_rho*u*v*ufl.dx + dt*ufl.dot(self.thermalCond*ufl.grad(u), ufl.grad(v)) * ufl.dx + + # source = self.globalSediments.rhp[self.numberOfSediments-1] * 1e-6 # conversion from uW/m^3 + # f = dolfinx.fem.Constant(self.mesh, PETSc.ScalarType(source)) # source term + f = self.rhpFcn * 1e-6 # conversion from uW/m^3 + print("mean RHP", np.mean(self.rhpFcn.x.array[:])) + + if ( self.useBaseFlux ): + # baseFlux = 0.03 if (self.tti>50) else 0.03 + baseFlux = self.baseFluxMagnitude + # define Neumann condition: constant flux at base + # expression g defines values of Neumann BC (heat flux at base) + x = ufl.SpatialCoordinate(self.mesh) + domain_c = dolfinx.fem.Function(self.V) + if (self.CGorder>1): + def marker(x): + print(x.shape, x) + return x[2,:]>3990 + facets = dolfinx.mesh.locate_entities_boundary(self.mesh, dim=(self.mesh.topology.dim - 2), + marker=marker ) + print(type(facets), facets.shape) + dofs = dolfinx.fem.locate_dofs_topological(V=self.V, entity_dim=1, entities=facets) + print( type(dofs), len(dofs)) + print(facets.shape, dofs.shape) + if (len(facets)>0): + print( np.amax(facets)) + if (len(dofs)>0): + print( np.amax(dofs)) + print(type(domain_c.x.array), len(domain_c.x.array)) + domain_c.x.array[ dofs ] = 1 + else: + basepos = self.getBaseAtMultiplePos(self.mesh.geometry.x[:,0], self.mesh.geometry.x[:,1]) + domain_c.x.array[ self.mesh.geometry.x[:,2] > basepos*0.99 ] = 1 + xmin, xmax = np.amin(self.mesh.geometry.x[:,0]), np.amax(self.mesh.geometry.x[:,0]) + ymin, ymax = np.amin(self.mesh.geometry.x[:,1]), np.amax(self.mesh.geometry.x[:,1]) + # + # remove corners from base heat flow domain + domain_c.x.array[ np.logical_and( self.mesh.geometry.x[:,0] < xmin+1, self.mesh.geometry.x[:,1] < ymin+1) ] = 0 + domain_c.x.array[ np.logical_and( self.mesh.geometry.x[:,0] < xmin+1, self.mesh.geometry.x[:,1] > ymax-1) ] = 0 + domain_c.x.array[ np.logical_and( self.mesh.geometry.x[:,0] > xmax-1, self.mesh.geometry.x[:,1] < ymin+1) ] = 0 + domain_c.x.array[ np.logical_and( self.mesh.geometry.x[:,0] > xmax-1, self.mesh.geometry.x[:,1] > ymax-1) ] = 0 + + domain_zero = dolfinx.fem.Function(self.V) + toppos = self.getSubsidenceAtMultiplePos(self.mesh.geometry.x[:,0], self.mesh.geometry.x[:,1]) + domain_zero.x.array[ self.mesh.geometry.x[:,2] < toppos+0.01 ] = 1 + print("Neumann conditions: ", self.tti, np.count_nonzero(domain_c.x.array), np.count_nonzero(domain_zero.x.array)) + + g = (-1.0*baseFlux) * ufl.conditional( domain_c > 0, 1.0, 0.0 ) + L = (self.c_rho*self.u_n + dt*f)*v*ufl.dx - dt * g * v * ufl.ds # last term reflects Neumann BC + else: + L = (self.c_rho*self.u_n + dt*f)*v*ufl.dx # no Neumann BC + + bilinear_form = dolfinx.fem.form(a) + linear_form = dolfinx.fem.form(L) + + A = dolfinx.fem.petsc.assemble_matrix(bilinear_form, bcs=[self.bc]) + A.assemble() + b = dolfinx.fem.petsc.create_vector(linear_form) + + from petsc4py import PETSc + solver = PETSc.KSP().create(self.mesh.comm) + + solver.setOperators(A) + solver.setType(PETSc.KSP.Type.PREONLY) + solver.getPC().setType(PETSc.PC.Type.LU) + + for i in range(num_steps): + t += dt + + # Update the right hand side reusing the initial vector + with b.localForm() as loc_b: + loc_b.set(0) + dolfinx.fem.petsc.assemble_vector(b, linear_form) + + # TODO: update Dirichlet BC at every time step: + # the temperature at the base of Asth is set such that it reaches Tm at the current depth of the LAB (using the slope adiab=0.0003) + # bc = self.buildDirichletBC() + + # Apply Dirichlet boundary condition to the vector + dolfinx.fem.petsc.apply_lifting(b, [bilinear_form], [[self.bc]]) + b.ghostUpdate(addv=PETSc.InsertMode.ADD_VALUES, mode=PETSc.ScatterMode.REVERSE) + + dolfinx.fem.petsc.set_bc(b, [self.bc]) + + # Solve linear problem + solver.solve(b, self.uh.vector) + self.uh.x.scatter_forward() + + # Update solution at previous time step (u_n) + # diffnorm = np.sum(np.abs(self.u_n.x.array - self.uh.x.array)) / self.u_n.x.array.shape[0] + self.u_n.x.array[:] = self.uh.x.array + + + + + # + # ===================================== + # Helper functions, not used by the main workflow + # + # ===================================== + # + + def safeInterpolation(self, interp, pos_x, pos_y, epsilon=1e-2): + # + # NDLinearInterpolator cannot extrapolate beyond the data points; + # use an epsilon to avoid NaN in sitations where the query point is marginally outside + # + res = interp([pos_x, pos_y])[0] + if (np.isnan(res)): + manyres = np.array( [ interp([pos_x-epsilon, pos_y-epsilon])[0], \ + interp([pos_x+epsilon, pos_y-epsilon])[0],\ + interp([pos_x-epsilon, pos_y+epsilon])[0],\ + interp([pos_x+epsilon, pos_y+epsilon])[0]]) + res = np.nanmean(manyres) + if (np.isnan(res)): + # print(pos_x, pos_y, interp([pos_x, pos_y]), interp([0,0])[0] ) + logger.warning(f'NaN encounered in safeInterpolation pos_x {pos_x}: pos_y: {pos_y}; {interp([pos_x, pos_y])} {interp([0,0])[0]} ') + # assert not np.isnan(res), "interpolation is nan in safeInterpolation" + return res + + def getThickOfCrustAtPos(self, tti, pos_x, pos_y): + interp = self.getInterpolator(tti, "thick_crust") + thick_crust_1 = self.safeInterpolation(interp, pos_x, pos_y) + assert not np.isnan(thick_crust_1), "interpolation is nan in thick crust!" + return thick_crust_1 + + def getTopOfCrustAtPos(self, tti, pos_x, pos_y): + interp = self.getInterpolator(tti, "top_crust") + top_crust_1 = self.safeInterpolation(interp, pos_x, pos_y) + assert not np.isnan(top_crust_1), "interpolation is nan in top crust!" + return top_crust_1 + + def getTopOfLithAtPos(self, tti, pos_x, pos_y): + interp = self.getInterpolator(tti, "topoflith") + top_lith_1 = self.safeInterpolation(interp, pos_x, pos_y) + assert not np.isnan(top_lith_1), "interpolation is nan in top lith!" + return top_lith_1 + + def getTopOfAsthAtPos(self, tti, pos_x, pos_y): + interp = self.getInterpolator(tti, "topofasth") + top_asth_1 = self.safeInterpolation(interp, pos_x, pos_y) + assert not np.isnan(top_asth_1), "interpolation is nan in top asth!" + return top_asth_1 + + def getSubsidenceAtPos(self, tti, pos_x, pos_y): + interp = self.getInterpolator(tti, "subsidence") + subs1 = self.safeInterpolation(interp, pos_x, pos_y) + assert not np.isnan(subs1), "interpolation is nan in subsidence!" + return subs1 + + def getSedPosAtPos(self, tti, pos_x, pos_y, sediment_id, use_top_instead_of_bottom=False): + interp = self.getInterpolator(tti, "sedimentpos", sed_id=sediment_id, use_top_instead_of_bottom=use_top_instead_of_bottom) + z_c_1 = self.safeInterpolation(interp, pos_x, pos_y) + return z_c_1 + + def getPosAtNode(self, tti, node_index, sediment_id, use_top_instead_of_bottom=False): + z_c = top_sed(self.node1D[node_index],tti) + if (use_top_instead_of_bottom): + z_c = z_c + top_sed_id(self.node1D[node_index], sediment_id, tti) + else: + z_c = z_c + bottom_sed_id(self.node1D[node_index], sediment_id, tti) + return z_c + + # + def findLayerID(self, tti, point): + """Helper function to determine the layer ID for the given point. Not used by the main simulation workflow + """ + px, py, pz = point[0],point[1],point[2] + subs = self.getSubsidenceAtPos(tti, px, py) + if (pz top_crust) and (pz<=top_lith): + return -1 + if (pz > top_lith) and (pz<=top_asth): + return -2 + if (pz > top_asth): + return -3 + for ss in range(self.numberOfSediments): + if (ss==0): + top_sed = self.getSedPosAtPos(tti, px, py, 0, use_top_instead_of_bottom=True) + else: + top_sed = self.getSedPosAtPos(tti, px, py, ss-1) + top_next_sed = self.getSedPosAtPos(tti, px, py, ss) + if ( ss == self.numberOfSediments-1): + top_next_sed = top_next_sed + 0.1 + if (pz >= top_sed) and (pz < top_next_sed): + return ss + return 100 + + + def interpolatorKey(self, tti, dataname, sed_id = -1, use_top_instead_of_bottom=False): + key = str(tti)+"_"+dataname + if (sed_id>=0): + key=key+"SED"+str(sed_id) + if (use_top_instead_of_bottom): + key=key+"TOP" + return key + + def getInterpolator(self, tti, dataname, sed_id = -1, use_top_instead_of_bottom=False): + key = self.interpolatorKey(tti, dataname, sed_id=sed_id, use_top_instead_of_bottom=use_top_instead_of_bottom) + if (key in self.interpolators): + return self.interpolators[key] + + xpos = [ node.X for node in self.node1D ] + ypos = [ node.Y for node in self.node1D ] + + val = None + if (dataname=="thick_crust"): + val = [ thick_crust(node, tti) for node in self.node1D ] + if (dataname=="top_crust"): + val = [ top_crust(node, tti) for node in self.node1D ] + if (dataname=="subsidence"): + val = [ top_sed(node, tti) for node in self.node1D ] + if (dataname=="sedimentpos"): + val = [ self.getPosAtNode(tti, i, sed_id, use_top_instead_of_bottom=use_top_instead_of_bottom) for i in range(len(self.node1D)) ] + if (dataname=="topoflith"): + val = [ self.getTopOfLithAtNode(tti, i) for i in range(len(self.node1D)) ] + if (dataname=="topofasth"): + val = [ self.getTopOfAsthAtNode(tti, i) for i in range(len(self.node1D)) ] + assert val is not None, "unknown interpolator datanme " + dataname + + interp = LinearNDInterpolator(list(zip(xpos, ypos)), val) + self.interpolators[key] = interp + return interp + + + def evaluateVolumes(self): + def boundary(x): + return np.full(x.shape[1], True) + + entities = dolfinx.mesh.locate_entities(self.mesh, 3, boundary ) + tet = dolfinx.cpp.mesh.entities_to_geometry(self.mesh, 3, entities, False) + + p0 = self.mesh.geometry.x[tet,:] + totalvol = 0 + num_sed = self.numberOfSediments + subvols = [0.0 for _ in range(num_sed+1)] + for i,t in enumerate(tet): + ps = p0[i] + ps = p0[i] + vol = self.volumeOfTet(ps) + lid = self.cell_data_layerID[i] + # lid = self.findLayerID(self.tti, midpoint) + commonset = self.layer_id_per_vertex[t[0]].intersection(self.layer_id_per_vertex[t[1]]).intersection(self.layer_id_per_vertex[t[2]]).intersection(self.layer_id_per_vertex[t[3]]) + lid = int(list(commonset)[0]) + + vol = self.volumeOfTet(ps) + totalvol = totalvol + vol + if (lid==-1): + subvols[num_sed] = subvols[num_sed] + vol + if (lid>=0) and (lid 0.001) or (d2 > 0.001) or (d3 > 0.001) + is_in_triangle = not (has_neg and has_pos) + return is_in_triangle + + # + def interpolateResult(self, x): + """interpolates the result at given positions x; + depends on dolfinx vertex-cell association functions which sometimes fail for no obvious reason.. + """ + + # + # the interpolation code is prone to problems, especially when vertices are ouside the mesh + # + tol = 1.0 # Avoid hitting the outside of the domain + tol_z = 1.0 # Avoid hitting the outside of the domain + plot_points = [] + meshZmax = np.amax(self.mesh.geometry.x[:,2]) + + midpoint = np.mean(self.mesh_vertices,axis=0) + mzm = [] + + transpose = x.shape[0]==3 and x.shape[1]!=3 + if transpose: + for xp in x.T: + fkey = self.floatKey2D([xp[0]+2e-2, xp[1]+2e-2]) + meshZmin = UniformNodeGridFixedSizeMeshModel.point_top_vertex_map.get(fkey, 1e10) + mzm.append(meshZmin) + meshZminV = np.array(mzm) + meshZminV2 = np.max([ x.T[:,2], meshZminV], axis=0) + else: + for xp in x: + fkey = self.floatKey2D([xp[0]+2e-2, xp[1]+2e-2]) + meshZmin = UniformNodeGridFixedSizeMeshModel.point_top_vertex_map.get(fkey, 1e10) + # meshZmin = self.getSubsidenceNew(self.tti, xp[0], xp[1]) + mzm.append(meshZmin) + meshZminV = np.array(mzm) + meshZminV2 = np.max([ x[:,2], meshZminV], axis=0) + + meshZminV3 = np.min([ meshZminV2, np.ones(meshZminV.shape) * meshZmax], axis=0) + meshZminV4 = meshZminV3.copy() + meshZminV4[meshZminV3midpoint[2]] = meshZminV3[meshZminV3>midpoint[2]] - tol_z + meshZminV4[meshZminV3>200000] = meshZminV3[meshZminV3>200000] - 100.0 + pl_po = x.T.copy() if transpose else x.copy() + pl_po[:,2] = meshZminV4 + + plot_points = [] + for i in range(pl_po.shape[0]): + pt = pl_po[i,:] + fkey = self.floatKey2D(pt) + on_edge = UniformNodeGridFixedSizeMeshModel.point_domain_edge_map.get(fkey, True) + dx, dy = 0.0, 0.0 + if on_edge: + if pt[0]midpoint[0]): + dx = -tol + if pt[1]midpoint[1]: + dy = -tol + plot_points.append( [ pt[0]+dx, pt[1]+dy, pt[2]] ) + plot_points = np.array(plot_points) + + bb_tree = dolfinx.geometry.BoundingBoxTree(self.mesh, self.mesh.topology.dim) + + points_cells = [] + points_on_proc = [] + + # Find cells whose bounding-box collide with the the points + cell_candidates = dolfinx.geometry.compute_collisions(bb_tree, plot_points) + + + res = [] + for i, point in enumerate(plot_points): + # if len(colliding_cells.links(i))>0: + # points_on_proc.append(point) + # points_cells.append(colliding_cells.links(i)[0]) + if len(cell_candidates.links(i))>0: + #points_on_proc.append(point) + #points_cells.append(cell_candidates.links(i)[0]) + for bb in cell_candidates.links(i): + val = self.uh.eval(point, [bb]) + if (not np.isnan(val)): + break + res.append( val ) + else: + print("need to extrapolate cell for point", i, point) + if (point[2]>200000): + try: + points_cells.append(cell_candidates.links(i)[0]) + points_on_proc.append(point) + except IndexError: + print("IndexError", point, cell_candidates.links(i) ) + breakpoint() + raise + else: + print("PING V", point) + if len(cell_candidates.links(i))==0: + print("PING V V", point) + def boundary(x): + return np.full(x.shape[1], True) + entities = dolfinx.mesh.locate_entities(self.mesh, 3, boundary ) + breakpoint() + points_on_proc.append(point) + points_cells.append(cell_candidates.links(i)[0]) + res = np.array(res) + aa = np.any(np.isnan(res)) + bb = np.any(np.isnan(self.uh.x.array)) + if aa or bb: + print(aa,bb) + breakpoint() + + if transpose: + assert res.flatten().shape[0] == x.shape[1] + else: + assert res.flatten().shape[0] == x.shape[0] + return res.flatten() + + def nodeIsOnDomainEdge(self, node0): + return any([ e[0]==node0 or e[1]==node0 for e in self.convexHullEdges]) + + def pointIsOnDomainEdge(self, pt, node0, node1, weight): + if (abs(weight)<0.01): + return self.nodeIsOnDomainEdge(node0) + if (abs(weight-1.0)<0.01): + return self.nodeIsOnDomainEdge(node1) + b0 = [node0, node1] in self.convexHullEdges + b1 = [node1, node0] in self.convexHullEdges + if b0 or b1: + return True + return False + + +def run( model:Model, run_simulation=True, start_time=182, end_time=0, out_dir = "out-mapA/"): + + + nums = 4 + dt = 314712e8 / nums + + mms2 = [] + mms_tti = [] + + tti = 0 + subvolumes = [] + + writeout = True + + if not run_simulation: + return + time_solve = 0.0 + + for tti in range(start_time, end_time-1,-1): #start from oldest + rebuild_mesh = (tti==start_time) + if rebuild_mesh: + print("Rebuild/reload mesh at tti=", tti) + mm2 = UniformNodeGridFixedSizeMeshModel(model, modelName="test"+str(tti)) + print("builing") + mm2.buildMesh(tti) + print("done") + else: + print("Re-generating mesh vertices at tti=", tti) + mm2.updateMesh(tti) + + print("===",tti,"=========== ") + if ( len(mms2) == 0): + tic() + mm2.setupSolverAndSolve(no_steps=40, time_step = 314712e8 * 2e2, skip_setup=False) + time_solve = time_solve + toc(msg="setup solver and solve") + else: + tic() + # mm2.setupSolverAndSolve( initial_state_model=mms2[-1], no_steps=nums, time_step=dt, skip_setup=(not rebuild_mesh)) + mm2.setupSolverAndSolve( no_steps=nums, time_step=dt, skip_setup=(not rebuild_mesh)) + time_solve = time_solve + toc(msg="setup solver and solve") + # subvolumes.append(mm2.evaluateVolumes()) + if (writeout): + tic() + mm2.writeLayerIDFunction(out_dir+"LayerID-"+str(tti)+".xdmf", tti=tti) + mm2.writeTemperatureFunction(out_dir+"Temperature-"+str(tti)+".xdmf", tti=tti) + # mm2.writeOutputFunctions(out_dir+"test4-"+str(tti)+".xdmf", tti=tti) + toc(msg="write function") + mms2.append(mm2) + mms_tti.append(tti) + print("total time solve: " , time_solve) + EPCfilename = mm2.write_hexa_mesh_resqml("temp/") + print("RESQML model written to: " , EPCfilename) + read_mesh_resqml(EPCfilename) # test reading of the .epc file \ No newline at end of file diff --git a/warmth/mesh_utils.py b/warmth/mesh_utils.py new file mode 100644 index 0000000..799b647 --- /dev/null +++ b/warmth/mesh_utils.py @@ -0,0 +1,110 @@ +from dataclasses import dataclass + +from warmth.build import single_node + +@dataclass +class NodeGrid: + origin_x: float + origin_y: float + num_nodes_x: int + num_nodes_y: int + start_index_x: int + start_index_y: int + step_index: int # including every N:th node (in x and y) + step_x: float # node separation in x + step_y: float # node separation in y + modelNamePrefix: str = "new_test_X_" + nodeDirectoryPrefix: str = "nodes-mapA/" + + +@dataclass +class NodeParameters1D: + shf: float = 30e-3 + hc: float = 30e3 + hw: float = 3.6e3 + hLith: float = 130e3 + kLith: float = 3.109 + kCrust: float = 2.5 + kAsth: float = 100 + rhp: float = 2 + crustliquid: float = 2500.0 + crustsolid: float = 2800.0 + lithliquid: float = 2700.0 + lithsolid: float = 3300.0 + asthliquid: float = 2700.0 + asthsolid: float = 3200.0 + T0: float = 5 + Tm: float = 1330.0 + qbase: float = 30e-3 + +def getNodeParameters(node): + # + # TODO: better implementation + # + xx = NodeParameters1D() + xx.shf = node.shf + xx.hc = node.hc + xx.hw = node.hw + xx.hLith = node.hLith + xx.kLith = getattr(node, 'kLith', 3.108) + xx.kCrust = node.kCrust + xx.kAsth = getattr(node, 'kAsth', 100) + xx.rhp = node.rhp + xx.crustliquid = node.crustliquid + xx.crustsolid = node.crustsolid + xx.lithliquid = node.lithliquid + xx.lithsolid = node.lithsolid + xx.asthliquid = node.asthliquid + xx.asthsolid = node.asthsolid + xx.T0 = node.T0 + xx.Tm = node.Tm + xx.qbase = node.qbase + return xx + + +def top_crust(nn, tti): + if (tti > nn.subsidence.shape[0]-1): + return 0.0 + return nn.subsidence[tti] + nn.sed_thickness_ls[tti] +def top_sed(nn:single_node, tti): + if (tti > nn.subsidence.shape[0]-1): + return 0.0 + return nn.subsidence[tti] +def thick_crust(nn, tti): + if (tti > nn.crust_ls.shape[0]-1): + return 0.0 + return nn.crust_ls[tti] +def thick_lith(nn, tti): + if (tti > nn.lith_ls.shape[0]-1): + return 0.0 + return nn.lith_ls[tti] +def top_lith(nn, tti): + return top_crust(nn,tti) + thick_crust(nn,tti) +def top_asth(nn, tti): + return 130e3+nn.subsidence[tti]+nn.sed_thickness_ls[tti] + # return thick_lith(nn,tti) + # return thick_lith(nn,tti) + top_lith(nn,tti) +def top_sed_id(nn, sed_id, tti): + if (tti > nn.sed.shape[2]-1): + return 0.0 + if (sed_id==100): + sed_id = 0 + return nn.sed[sed_id,0,tti] +def bottom_sed_id(nn, sed_id, tti): + if (tti > nn.sed.shape[2]-1): + return 0.0 + if (sed_id==100): + sed_id = 0 + return nn.sed[sed_id,1,tti] +def thick_sed(nn, sed_id, tti): + return bottom_sed_id(nn,sed_id,tti) - top_sed_id(nn,sed_id,tti) + +def volumeOfTet(points): + """ Computes the volume of a tetrahedron, given as four 3D-points + """ + import numpy as np + ad = points[0]-points[3] + bd = points[1]-points[3] + cd = points[2]-points[3] + bdcd = np.cross(bd,cd) + return np.linalg.norm(np.dot(ad,bdcd))/6 diff --git a/warmth/parameters.py b/warmth/parameters.py index 8ff725c..b9dd4f7 100644 --- a/warmth/parameters.py +++ b/warmth/parameters.py @@ -45,7 +45,7 @@ def __init__(self) -> None: self.maxContLith: float = 130000.0 self.starting_beta: float = 1.1 self.positive_down = True - self.out_path:Path=Path('./simout') + pass @property diff --git a/warmth/postprocessing.py b/warmth/postprocessing.py index 8bf6b61..0dd0f5b 100644 --- a/warmth/postprocessing.py +++ b/warmth/postprocessing.py @@ -1,6 +1,9 @@ +import time from typing import Tuple, TypedDict +from scipy import interpolate import numpy as np import pandas as pd +from .logging import logger class Results: @@ -331,3 +334,102 @@ def _filter_sed_id_index(self,sed_id:int,sed_id_arr:np.ndarray)->Tuple[int,int]: return top_sediment_index,base_sediment_index else: raise Exception(f"Invalid sediment id {sed_id}. Valid ids: {np.unique(sed_id_arr[~np.isnan(sed_id_arr)])}") + +class Results_interpolator: + def __init__(self, builder,n_valid_node:int) -> None: + self._builder = builder + self._values = ["kAsth","shf","qbase"] + self._values_arr = ["subsidence","crust_ls","lith_ls"] + self._n_age=None + self.n_valid_node= n_valid_node+1 + self._x = None + self._y=None + pass + + def iter_full_sim_nodes(self): + for node in self._builder.iter_node(): + if node._full_simulation: + yield node + + + def _get_x_y(self)->None: + x = np.zeros(self.n_valid_node) + y = np.zeros(self.n_valid_node) + for count, node in enumerate(self.iter_full_sim_nodes()): + x[count]=node.X + y[count]= node.Y + if count == 0: + self._n_age = node.crust_ls.size + self._x = x + self._y=y + return + + @property + def x(self)->np.ndarray[np.float64]: + if isinstance(self._x,type(None)): + self._get_x_y() + return self._x + @property + def y(self)->np.ndarray[np.float64]: + if isinstance(self._y,type(None)): + self._get_x_y() + return self._y + @property + def n_age(self)->int: + if isinstance(self._n_age,type(None)): + self._get_x_y() + return self._n_age + + def interpolator(self,val): + grid = self._builder.grid + grid_x, grid_y = np.mgrid[ + grid.origin_x: grid.xmax: grid.step_x, + grid.origin_y: grid.ymax: grid.step_y, + ] + rbfi = interpolate.Rbf(self.x, self.y, val) + di = rbfi(grid_x, grid_y) + return di + + def interp_value(self): + for prop in self._values: + logger.warning(f"Interpolating {prop}") + val = np.zeros(self.n_valid_node) + for count, node in enumerate(self.iter_full_sim_nodes()): + val[count] = getattr(node,prop) + + interped = self.interpolator(val) + for n in self._builder.iter_node(): + if n._full_simulation is False: + idx = n.indexer + val =interped[idx[0],idx[1]] + setattr(n,prop,val) + return + + def interp_arr(self): + for prop in self._values_arr: + logger.warning(f"Interpolating {prop}") + #extract all data from all full simulated nodes + val = np.zeros((self.n_valid_node,self.n_age)) + for count, node in enumerate(self.iter_full_sim_nodes()): + val[count,:] = getattr(node,prop) + #Handle not simulated nodes + prop ="_"+prop + for age in range(self.n_age): + # filter to age + interp_all_this_age = self.interpolator(val[:,age]) + #set the nodes + for node in self._builder.iter_node(): + if node._full_simulation is False: + if isinstance(getattr(node,prop),type(None)): + setattr(node,prop,np.zeros(self.n_age)) + idx = node.indexer + interpolated_val =interp_all_this_age[idx[0],idx[1]] + arr = getattr(node,prop) + arr[age] =interpolated_val + setattr(node,prop,arr) + return + + def run(self): + self.interp_value() + self.interp_arr() + return \ No newline at end of file diff --git a/warmth/resqpy_helpers.py b/warmth/resqpy_helpers.py new file mode 100644 index 0000000..5ad0c0c --- /dev/null +++ b/warmth/resqpy_helpers.py @@ -0,0 +1,460 @@ +# https://resqpy.readthedocs.io/en/latest/tutorial/high_level_objects.html#reading-and-writing-objects + +import numpy as np + +import resqpy.property as rqp +import resqpy.crs as rqc +import resqpy.model as rq +import resqpy.olio.uuid as bu +import resqpy.unstructured as rug + + +# +# Our example resqml model can be read using the read_mesh_resqml function below.. +# read_mesh_resqml("/path/mapA-961-nodes-182_0.epc") +# +# + +def read_mesh_resqml(epcfilename, meshTitle = 'tetramesh'): + """Example code how to read the .epc file written by the write_tetra_grid_with_properties function. + Extracts arrays of node positions and of tetrahedra indices. + Extracts arrays of properties (per-cell and per-node) + """ + model = rq.Model(epcfilename) + assert model is not None + + # + # read mesh: vertex positions and cell/tetrahedra definitions + # + tetra_uuid = model.uuid(obj_type = 'UnstructuredGridRepresentation', title = meshTitle) + assert tetra_uuid is not None + tetra = rug.TetraGrid(model, uuid = tetra_uuid) + assert tetra is not None + print(tetra.title, tetra.node_count, tetra.cell_count, tetra.cell_shape) + assert tetra.cell_shape == 'tetrahedral' + + print( tetra.points_ref().shape ) # numpy array of vertex positions + cells = np.array( [ tetra.distinct_node_indices_for_cell(i) for i in range(tetra.cell_count) ] ) # cell indices are read using this function(?) + print( cells.shape ) # numpy array of vertex positions + + tetra.check_tetra() + + # + # read properties + # + + temp_uuid = model.uuid(title = 'Temperature') + assert temp_uuid is not None + temp_prop = rqp.Property(model, uuid = temp_uuid) + assert temp_prop.uom() == 'degC' + assert temp_prop.indexable_element() == 'nodes' # properties are defined either on nodes or on cells + print( temp_prop.array_ref().shape, temp_prop.array_ref()[0:10] ) # .array_ref() exposes the values as numpy array + + layerID_uuid = model.uuid(title = 'LayerID') + assert layerID_uuid is not None + layerID_prop = rqp.Property(model, uuid = layerID_uuid) + # assert layerID_prop.uom() == 'Euc' + assert layerID_prop.is_continuous() == False + assert layerID_prop.indexable_element() == 'cells' + print( layerID_prop.array_ref().shape, layerID_prop.array_ref()[0:10] ) # .array_ref() exposes the values as numpy array + + titles=['Temperature', 'Age', 'LayerID', 'Porosity_initial', 'Porosity_decay', 'Density_solid', 'insulance_thermal', 'Radiogenic_heat_production'] + for title in titles: + prop_uuid = model.uuid(title = title) + prop = rqp.Property(model, uuid = prop_uuid) + print(title, prop.indexable_element(), prop.uom(), prop.array_ref()[0:10] ) + +def write_tetra_grid_with_properties(filename, nodes, cells, modelTitle = "tetramesh", + Temp_per_vertex=None, age_per_vertex=None, poro0_per_cell=None, decay_per_cell=None, density_per_cell=None, + cond_per_cell=None, rhp_per_cell=None, lid_per_cell=None ): + """Writes the given tetrahedral mesh, defined by arrays of nodes and cell indices, into a RESQML .epc file + Given SubsHeat properties are optionally written. + NOTE: writing properties that are defines per-node (have 'nodes' as indexable element) requires a patched version of resqpy! + """ + node_count = len(nodes) + faces_per_cell = [] + nodes_per_face = [] + faces_dict = {} + faces_repeat = np.zeros(node_count*100, dtype = bool) + cell_face_is_right_handed = np.zeros(len(cells)*4, dtype = bool) + for it,tet in enumerate(cells): + midp = ( nodes[tet[0],:] + nodes[tet[1],:] + nodes[tet[2],:] + nodes[tet[3],:] ) * 0.25 + for ir,tri in enumerate([[0,1,2],[0,1,3],[1,2,3],[2,0,3]]): + face0 = [tet[x] for x in tri ] + assert -1 not in face0 + + # + # the point order in the tetrahedra may not be consistent + # best to test every face individually + # + e0 = nodes[face0[1],:] - nodes[face0[0],:] + e1 = nodes[face0[2],:] - nodes[face0[0],:] + normal = np.cross(e0,e1) + midp_face = ( nodes[face0[0],:] + nodes[face0[1],:] + nodes[face0[2],:]) / 3.0 + sign = np.dot( midp-midp_face, normal ) + face_handedness = (sign>0) + + fkey0 = ( x for x in sorted(face0) ) + # + # keep track of which faces are encountered once vs. more than once + # faces that are encountered the second time will need to use the reverse handedness + # + face_is_repeated = False + if (fkey0 not in faces_dict): + faces_dict[fkey0] = len(nodes_per_face) + nodes_per_face.extend(face0) + cell_face_is_right_handed[it*4+ir] = face_handedness + else: + face_is_repeated = True + cell_face_is_right_handed[it*4+ir] = not face_handedness + fidx0 = faces_dict.get(fkey0) + faces_per_cell.append(fidx0/3) + faces_repeat[int(fidx0/3)] = face_is_repeated + + set_cell_count = int(len(faces_per_cell)/4) + face_count = int(len(nodes_per_face)/3) + + # cell_face_is_right_handed = np.zeros(face_count, dtype = bool) + # cell_face_is_right_handed[faces_repeat[0:face_count]] = True + + model = rq.new_model(filename) + crs = rqc.Crs(model) + crs.create_xml() + + # create an empty TetraGrid + tetra = rug.TetraGrid(model, title = modelTitle) + assert tetra.cell_shape == 'tetrahedral' + + # hand craft all attribute data + tetra.crs_uuid = model.uuid(obj_type = 'LocalDepth3dCrs') + assert tetra.crs_uuid is not None + assert bu.matching_uuids(tetra.crs_uuid, crs.uuid) + tetra.set_cell_count(set_cell_count) + # faces + tetra.face_count = face_count + tetra.faces_per_cell_cl = np.arange(4, 4 * set_cell_count + 1, 4, dtype = int) + tetra.faces_per_cell = np.array(faces_per_cell) + + # nodes + tetra.node_count = node_count + tetra.nodes_per_face_cl = np.arange(3, 3 * face_count + 1, 3, dtype = int) + tetra.nodes_per_face = np.array(nodes_per_face) + + # face handedness + tetra.cell_face_is_right_handed = cell_face_is_right_handed # False for all faces for external cells (1 to 4) + + # points + tetra.points_cached = nodes + + # basic validity check + tetra.check_tetra() + + # write arrays, create xml and store model + tetra.write_hdf5() + tetra.create_xml() + + # https://github.com/bp/resqpy/blob/master/tests/unit_tests/property/test_property.py + # + # 'Temperature' 'Age' 'LayerID' 'Porosity_initial' 'Porosity_decay' 'Density_solid' 'insulance_thermal''Radiogenic_heat_production' + + if Temp_per_vertex is not None: + _ = rqp.Property.from_array(model, + Temp_per_vertex, + source_info = 'SubsHeat', + keyword = 'Temperature', + support_uuid = tetra.uuid, + property_kind = 'thermodynamic temperature', + indexable_element = 'nodes', + uom = 'degC') + + if age_per_vertex is not None: + _ = rqp.Property.from_array(model, + age_per_vertex, + source_info = 'SubsHeat', + keyword = 'Age', + support_uuid = tetra.uuid, + property_kind = 'geological age', + indexable_element = 'nodes', + uom = 'Ma') + + if lid_per_cell is not None: + _ = rqp.Property.from_array(model, + lid_per_cell.astype(np.int32), + source_info = 'SubsHeat', + keyword = 'LayerID', + support_uuid = tetra.uuid, + property_kind = 'layer ID', + indexable_element = 'cells', + uom = 'Euc', + discrete=True) + + if poro0_per_cell is not None: + _ = rqp.Property.from_array(model, + poro0_per_cell, + source_info = 'SubsHeat', + keyword = 'Porosity_initial', + support_uuid = tetra.uuid, + property_kind = 'porosity', + indexable_element = 'cells', + uom = 'm3/m3') + if decay_per_cell is not None: + _ = rqp.Property.from_array(model, + decay_per_cell, + source_info = 'SubsHeat', + keyword = 'Porosity_decay', + support_uuid = tetra.uuid, + property_kind = 'porosity decay', + indexable_element = 'cells', + uom = 'Euc') + if density_per_cell is not None: + _ = rqp.Property.from_array(model, + density_per_cell, + source_info = 'SubsHeat', + keyword = 'Density_solid', + support_uuid = tetra.uuid, + property_kind = 'density', + indexable_element = 'cells', + uom = 'kg/m3') + if cond_per_cell is not None: + _ = rqp.Property.from_array(model, + np.reciprocal(cond_per_cell), + source_info = 'SubsHeat', + keyword = 'insulance_thermal', + support_uuid = tetra.uuid, + property_kind = 'thermal insulance', + indexable_element = 'cells', + uom = 'deltaK.m2/W') + if rhp_per_cell is not None: + _ = rqp.Property.from_array(model, + rhp_per_cell, + source_info = 'SubsHeat', + keyword = 'Radiogenic_heat_production', + support_uuid = tetra.uuid, + property_kind = 'heat', + indexable_element = 'cells', + uom = 'W/m3') + + model.store_epc() + # read_mesh_resqml(filename) + + +def read_mesh_resqml_hexa(epcfilename, meshTitle = 'hexamesh'): + """Example code how to read the .epc file written by the write_hexa_grid_with_properties function. + Extracts arrays of node positions and of hexahedra indices. + Extracts arrays of properties (per-cell and per-node) + """ + model = rq.Model(epcfilename) + assert model is not None + + # + # read mesh: vertex positions and cell definitions + # + hexa_uuid = model.uuid(obj_type = 'UnstructuredGridRepresentation', title = meshTitle) + assert hexa_uuid is not None + hexa = rug.HexaGrid(model, uuid = hexa_uuid) + assert hexa is not None + print(hexa.title, hexa.node_count, hexa.cell_count, hexa.cell_shape) + assert hexa.cell_shape == 'hexahedral' + + print( hexa.points_ref().shape ) # numpy array of vertex positions + cells = np.array( [ hexa.distinct_node_indices_for_cell(i) for i in range(hexa.cell_count) ] ) # cell indices are read using this function(?) + print( cells.shape ) # numpy array of vertex positions + + hexa.check_hexahedral() + + # + # read properties + # + + temp_uuid = model.uuid(title = 'Temperature') + assert temp_uuid is not None + temp_prop = rqp.Property(model, uuid = temp_uuid) + assert temp_prop.uom() == 'degC' + assert temp_prop.indexable_element() == 'nodes' # properties are defined either on nodes or on cells + print( temp_prop.array_ref().shape, temp_prop.array_ref()[0:10] ) # .array_ref() exposes the values as numpy array + + layerID_uuid = model.uuid(title = 'LayerID') + assert layerID_uuid is not None + layerID_prop = rqp.Property(model, uuid = layerID_uuid) + # assert layerID_prop.uom() == 'Euc' + assert layerID_prop.is_continuous() == False + assert layerID_prop.indexable_element() == 'cells' + print( layerID_prop.array_ref().shape, layerID_prop.array_ref()[0:10] ) # .array_ref() exposes the values as numpy array + + titles=['Temperature', 'Age', 'LayerID', 'Porosity_initial', 'Porosity_decay', 'Density_solid', 'insulance_thermal', 'Radiogenic_heat_production'] + for title in titles: + prop_uuid = model.uuid(title = title) + prop = rqp.Property(model, uuid = prop_uuid) + print(title, prop.indexable_element(), prop.uom(), prop.array_ref()[0:10] ) + + +def write_hexa_grid_with_properties(filename, nodes, cells, modelTitle = "hexamesh", + Temp_per_vertex=None, age_per_vertex=None, poro0_per_cell=None, decay_per_cell=None, density_per_cell=None, + cond_per_cell=None, rhp_per_cell=None, lid_per_cell=None ): + """Writes the given hexahedral mesh, defined by arrays of nodes and cell indices, into a RESQML .epc file + Given SubsHeat properties are optionally written. + + cells is an array of 8-arrays in which the nodes are ordered: + 7------6 + / /| + / / | + 4------5 | + | | + | 3------2 + | / / + |/ / + 0------1 + + NOTE: writing properties that are defines per-node (have 'nodes' as indexable element) requires a patched version of resqpy! + """ + node_count = len(nodes) + faces_per_cell = [] + nodes_per_face = [] + faces_dict = {} + faces_repeat = np.zeros(node_count*100, dtype = bool) + + cell_face_is_right_handed = np.zeros( len(cells)*6, dtype = bool) + for ih,hexa in enumerate(cells): + faces= [[0,3,2,1], [0,1,5,4], [1,2,6,5], [2,3,7,6], [3,0,4,7], [4,5,6,7]] + for iq,quad in enumerate(faces): + face0 = [hexa[x] for x in quad ] + assert -1 not in face0 + fkey0 = ( x for x in sorted(face0) ) + # + # keep track of which faces are encountered once vs. more than once + # faces that are encountered the second time will need to use the reverse handedness + # + face_is_repeated = False + if (fkey0 not in faces_dict): + faces_dict[fkey0] = len(nodes_per_face) + nodes_per_face.extend(face0) + cell_face_is_right_handed[(ih*6 + iq)] = False + else: + face_is_repeated = True + cell_face_is_right_handed[(ih*6 + iq)] = True + fidx0 = faces_dict.get(fkey0) + faces_per_cell.append(fidx0/4) + faces_repeat[int(fidx0/4)] = face_is_repeated + + set_cell_count = int(len(faces_per_cell)/6) + face_count = int(len(nodes_per_face)/4) + + + model = rq.new_model(filename) + crs = rqc.Crs(model) + crs.create_xml() + + # create an empty HexaGrid + hexa = rug.HexaGrid(model, title = modelTitle) + assert hexa.cell_shape == 'hexahedral' + + # hand craft all attribute data + hexa.crs_uuid = model.uuid(obj_type = 'LocalDepth3dCrs') + assert hexa.crs_uuid is not None + assert bu.matching_uuids(hexa.crs_uuid, crs.uuid) + hexa.set_cell_count(set_cell_count) + # faces + hexa.face_count = face_count + hexa.faces_per_cell_cl = np.arange(6, 6 * set_cell_count + 1, 6, dtype = int) + hexa.faces_per_cell = np.array(faces_per_cell) + + # nodes + hexa.node_count = node_count + hexa.nodes_per_face_cl = np.arange(4, 4 * face_count + 1, 4, dtype = int) + hexa.nodes_per_face = np.array(nodes_per_face) + + # face handedness + hexa.cell_face_is_right_handed = cell_face_is_right_handed # False for all faces for external cells + + # points + hexa.points_cached = nodes + + # basic validity check + hexa.check_hexahedral() + + # write arrays, create xml and store model + hexa.write_hdf5() + hexa.create_xml() + + if Temp_per_vertex is not None: + _ = rqp.Property.from_array(model, + Temp_per_vertex, + source_info = 'SubsHeat', + keyword = 'Temperature', + support_uuid = hexa.uuid, + property_kind = 'thermodynamic temperature', + indexable_element = 'nodes', + uom = 'degC') + + if age_per_vertex is not None: + _ = rqp.Property.from_array(model, + age_per_vertex, + source_info = 'SubsHeat', + keyword = 'Age', + support_uuid = hexa.uuid, + property_kind = 'geological age', + indexable_element = 'nodes', + uom = 'y') + + if lid_per_cell is not None: + _ = rqp.Property.from_array(model, + lid_per_cell.astype(np.int32), + source_info = 'SubsHeat', + keyword = 'LayerID', + support_uuid = hexa.uuid, + property_kind = 'layer ID', + indexable_element = 'cells', + uom = 'Euc', + discrete=True) + + if poro0_per_cell is not None: + _ = rqp.Property.from_array(model, + poro0_per_cell, + source_info = 'SubsHeat', + keyword = 'Porosity_initial', + support_uuid = hexa.uuid, + property_kind = 'porosity', + indexable_element = 'cells', + uom = 'm3/m3') + if decay_per_cell is not None: + _ = rqp.Property.from_array(model, + decay_per_cell, + source_info = 'SubsHeat', + keyword = 'Porosity_decay', + support_uuid = hexa.uuid, + property_kind = 'porosity decay', + indexable_element = 'cells', + uom = 'Euc') + if density_per_cell is not None: + _ = rqp.Property.from_array(model, + density_per_cell, + source_info = 'SubsHeat', + keyword = 'Density_solid', + support_uuid = hexa.uuid, + property_kind = 'density', + indexable_element = 'cells', + uom = 'kg/m3') + if cond_per_cell is not None: + # + # we write thermal conductivity as its inverse, the thermal insulance + # + _ = rqp.Property.from_array(model, + np.reciprocal(cond_per_cell), + source_info = 'SubsHeat', + keyword = 'insulance_thermal', + support_uuid = hexa.uuid, + property_kind = 'thermal insulance', + indexable_element = 'cells', + uom = 'deltaK.m2/W') + if rhp_per_cell is not None: + _ = rqp.Property.from_array(model, + rhp_per_cell, + source_info = 'SubsHeat', + keyword = 'Radiogenic_heat_production', + support_uuid = hexa.uuid, + property_kind = 'heat', + indexable_element = 'cells', + uom = 'W/m3') + + model.store_epc() + diff --git a/warmth/simulator.py b/warmth/simulator.py index 3b3f673..65c61f7 100644 --- a/warmth/simulator.py +++ b/warmth/simulator.py @@ -5,6 +5,7 @@ import time import numpy as np +from warmth.postprocessing import Results_interpolator from warmth.utils import load_pickle from .logging import logger from .forward_modelling import Forward_model @@ -23,6 +24,7 @@ def __init__(self, args) -> None: self.node_path:Path = args[1] self.node=load_node(self.node_path) self.parameters = load_pickle(self.parameter_path) + self.out_path=args[2] pass def _pad_sediments(self): @@ -37,7 +39,7 @@ def _pad_sediments(self): def _save_results(self) -> Path: filename = self.node._name+"_results" - filepath = self.parameters.out_path / NODES_DIR/filename + filepath = self.out_path / NODES_DIR/filename self.node._dump(filepath) return filepath @@ -55,10 +57,8 @@ def run(self) -> Path: # Delete input node self.node_path.unlink(missing_ok=True) except Exception as e: - import sys - self.node.error = repr(e.with_traceback(sys.exception().__traceback__)) + self.node.error = e filepath = self._save_results() - print(self.node.error) logger.error(self.node.error) return filepath @@ -68,7 +68,6 @@ def runWorker(args): result_path = worker.run() return result_path - class Simulator: """Solving model """ @@ -88,18 +87,20 @@ def __init__(self, builder: Builder) -> None: None) self.process = 2 self.cpu=self.process + self.simulate_every = 1 + self.out_path:Path=Path('./simout') pass @property def _nodes_path(self): - return self._builder.parameters.out_path / NODES_DIR + return self.out_path / NODES_DIR @property def _parameters_path(self): - return self._builder.parameters.out_path / PARAMETER_FILE + return self.out_path / PARAMETER_FILE @property def _grid_path(self): - return self._builder.parameters.out_path / GRID_FILE + return self.out_path / GRID_FILE @@ -120,17 +121,17 @@ def dump_input_data(self): futures = [th.submit(self.dump_input_nodes, i) for i in self._builder.iter_node() if i is not False] for future in concurrent.futures.as_completed(futures): - p.append([parameter_data_path, future.result()]) + p.append([parameter_data_path, future.result(),self.out_path]) return p def setup_directory(self, purge=False): - if self._builder.parameters.out_path.exists(): + if self.out_path.exists(): if purge: from shutil import rmtree - rmtree(self._builder.parameters.out_path) + rmtree(self.out_path) else: raise Exception( - f'Output directory {self._builder.parameters.out_path} already exist. Use purge=True to delete existing data') + f'Output directory {self.out_path} already exist. Use purge=True to delete existing data') self._nodes_path.mkdir(parents=True, exist_ok=True) return @@ -138,12 +139,46 @@ def run(self, save=False,purge=False,parallel=True): if parallel: self._parellel_run(save,purge) else: + if self.simulate_every != 1: + logger.warning("Serial simulation will run full simulation on all nodes") for i in self._builder.iter_node(): self.forward_modelling.current_node=i self.forward_modelling.simulate_single_node() return + def _filter_full_sim(self)->int: + count=0 + minimum_node_per_axis=5 + if self.simulate_every < 1: + raise Exception("Invalid input") + short_axis_count = self._builder.grid.num_nodes_x if self._builder.grid.num_nodes_x 0): + pass + else: + if isinstance(self._builder.nodes[index[0]][index[1]],bool) is False: + self._builder.nodes[index[0]][index[1]]._full_simulation = False + count+=1 + #for cols + if (index[1] % self.simulate_every > 0): + pass + else: + if isinstance(self._builder.nodes[index[0]][index[1]],bool) is False: + self._builder.nodes[index[0]][index[1]]._full_simulation = False + count+=1 + + if count >0: + logger.info(f"Setting {count} nodes to partial simulation") + return count def _parellel_run(self, save,purge): + filtered = self._filter_full_sim() self.setup_directory(purge) p = self.dump_input_data() #self._builder.nodes=self._builder.grid.make_grid_arr() @@ -171,7 +206,11 @@ def _parellel_run(self, save,purge): logger.warning(f"No result file for node X:{n.X}, Y:{n.Y}") if save==False: from shutil import rmtree - rmtree(self._builder.parameters.out_path) + rmtree(self.out_path) + if filtered >0: + logger.info(f"Interpolating results back to {filtered} partial simulated nodes") + interp_res= Results_interpolator(self._builder,len(p)-filtered) + interp_res.run() return def put_node_to_grid(self,node:single_node): self._builder.nodes[node.indexer[0]][node.indexer[1]]=node diff --git a/warmth/utils.py b/warmth/utils.py index 1ca3b30..96e6d90 100644 --- a/warmth/utils.py +++ b/warmth/utils.py @@ -1,8 +1,10 @@ -#from numba import jit, prange +# from numba import jit, prange # import gzip from pathlib import Path import pickle import numpy as np +from scipy import interpolate + from .logging import logger # https://gist.github.com/kadereub/9eae9cff356bb62cdbd672931e8e5ec4 @@ -126,16 +128,29 @@ # return x1, steps_taken -def compressed_pickle_save(data,path:Path): +def compressed_pickle_save(data, path: Path): with open(path, "wb") as f: pickle.dump(data, f, protocol=pickle.HIGHEST_PROTOCOL) -def compressed_pickle_open(path:Path): + +def compressed_pickle_open(path: Path): with open(path, "rb") as f: data = pickle.load(f) return data -def load_pickle(filepath:Path): + + +def load_pickle(filepath: Path): logger.info(f"Loading data from {filepath}") with open(filepath, "rb") as f: data = pickle.load(f) - return data \ No newline at end of file + return data + + +def interpolator(x, y, val, grid): + grid_x, grid_y = np.mgrid[ + grid.origin_x: grid.xmax+grid.step_x: grid.step_x, + grid.origin_yn: grid.ymax+grid.step_y: grid.step_y, + ] + rbfi = interpolate.Rbf(x, y, val) + di = rbfi(grid_x, grid_y) + return di From acd94bf5ad1ab762bdcd87a475c7aeeef67c56c6 Mon Sep 17 00:00:00 2001 From: Adam Cheng <52572642+adamchengtkc@users.noreply.github.com> Date: Mon, 6 Nov 2023 14:01:24 +0000 Subject: [PATCH 16/21] ENH: 3D code --- docs/notebooks/3D_simulation.ipynb | 246 ++++----- docs/notebooks/Build_within_Python.ipynb | 301 ++++++++++- docs/notebooks/data/0.gri | 4 +- docs/notebooks/data/100.gri | 4 +- docs/notebooks/data/163.gri | 4 +- docs/notebooks/data/168.gri | 4 +- docs/notebooks/data/170.gri | 4 +- docs/notebooks/data/182.gri | 4 +- docs/notebooks/data/66.gri | 4 +- .../{parallel-gmsh.py => __parallel-gmsh.py} | 0 subsheat3D/fixed_mesh_model.py | 2 +- subsheat3D/parallel-1Dsed.py | 16 +- tests/__test_dol.py | 322 +++++++++++ tests/temperer3D_mapA_example.py | 2 +- tests/test_from_grid.py | 498 +++++++++--------- tests/test_integration.py | 4 +- warmth/build.py | 14 +- warmth/mesh_model.py | 6 +- warmth/postprocessing.py | 2 +- 19 files changed, 987 insertions(+), 454 deletions(-) rename subsheat3D/{parallel-gmsh.py => __parallel-gmsh.py} (100%) create mode 100644 tests/__test_dol.py diff --git a/docs/notebooks/3D_simulation.ipynb b/docs/notebooks/3D_simulation.ipynb index 55bd711..f0ab58f 100644 --- a/docs/notebooks/3D_simulation.ipynb +++ b/docs/notebooks/3D_simulation.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -10,10 +10,50 @@ "from pathlib import Path" ] }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.03" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "1e-6*30e3" + ] + }, { "cell_type": "code", "execution_count": 2, "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.03" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "30e-3" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, "outputs": [], "source": [ "maps_dir = Path(\"./data/\")" @@ -21,7 +61,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -30,7 +70,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -39,106 +79,9 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
AgeFile_nameFacies_mapsStratigraphy
000.griNoneOnlap
16666.griNoneOnlap
2100100.griNoneOnlap
3163163.griNoneErosive
4168168.griNoneErosive
5170170.griNoneOnlap
6182182.griNoneErosive
\n", - "
" - ], - "text/plain": [ - " Age File_name Facies_maps Stratigraphy\n", - "0 0 0.gri None Onlap\n", - "1 66 66.gri None Onlap\n", - "2 100 100.gri None Onlap\n", - "3 163 163.gri None Erosive\n", - "4 168 168.gri None Erosive\n", - "5 170 170.gri None Onlap\n", - "6 182 182.gri None Erosive" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "#Add surface grids to the table. You can use other method as well\n", "inputs.loc[0]=[0,\"0.gri\",None,\"Onlap\"]\n", @@ -154,7 +97,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -164,7 +107,16 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model.builder.grid.__dict__" + ] + }, + { + "cell_type": "code", + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -173,27 +125,16 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "182" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "model.parameters.time_start" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -203,7 +144,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -213,7 +154,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -222,55 +163,58 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "102" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "model.builder.n_valid_node" ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "model.simulator.simulate_every=1" + "model.builder.node" ] }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model.builder.iter_node()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, "metadata": {}, "outputs": [ { - "name": "stderr", - "output_type": "stream", - "text": [ - "Processing... |################################| 102/102\n", - "\u001b[?25h" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CPU times: user 471 ms, sys: 224 ms, total: 696 ms\n", - "Wall time: 31.6 s\n" + "ename": "NameError", + "evalue": "name 'model' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m/workspaces/warmth/docs/notebooks/3D_simulation.ipynb Cell 14\u001b[0m line \u001b[0;36m1\n\u001b[0;32m----> 1\u001b[0m \u001b[39mfor\u001b[39;00m i \u001b[39min\u001b[39;00m model\u001b[39m.\u001b[39mbuilder\u001b[39m.\u001b[39mnode:\n\u001b[1;32m 2\u001b[0m \u001b[39mprint\u001b[39m(i)\n", + "\u001b[0;31mNameError\u001b[0m: name 'model' is not defined" ] } ], + "source": [ + "for i in model.builder.node:\n", + " print(i)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "%%time\n", "model.simulator.run(save=True,purge=True)" @@ -278,7 +222,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 15, "metadata": {}, "outputs": [ { @@ -288,7 +232,7 @@ "Rebuild/reload mesh at tti= 182\n", "Using 1D Node parameters NodeParameters1D(shf=0.03, hc=30000.0, hw=3600.0, hLith=130000.0, kLith=3.109, kCrust=2.5, kAsth=100, rhp=2, crustliquid=2500.0, crustsolid=2800.0, lithliquid=2700.0, lithsolid=3300.0, asthliquid=2700.0, asthsolid=3200.0, T0=5, Tm=1330.0, qbase=0.03)\n", "builing\n", - "saving\n" + "buildVertices\n" ] }, { diff --git a/docs/notebooks/Build_within_Python.ipynb b/docs/notebooks/Build_within_Python.ipynb index cd9ccf1..56fef24 100644 --- a/docs/notebooks/Build_within_Python.ipynb +++ b/docs/notebooks/Build_within_Python.ipynb @@ -9,7 +9,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": { "scrolled": true }, @@ -24,7 +24,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -33,7 +33,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -62,9 +62,177 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/workspaces/warmth/warmth/build.py:199: FutureWarning: In a future version, the Index constructor will not infer numeric dtypes when passed object-dtype sequences (matching Series behavior)\n", + " check_ascending = df.apply(lambda x: x.is_monotonic_increasing)\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
toptopagek_condrhpphidecaysolidusliquidusbasebaseagethicknessgrain_thicknessphi_mean
0152.001.5000002.301755e-090.6200000.5002720.02448.0810.020658.00.3103570.528332
1810.0201.5384622.079433e-090.5997300.4902708.02437.21608.066798.00.5110620.359571
21608.0661.5000002.301755e-090.2000000.5002720.02448.01973.0100365.00.3327800.088275
31973.01001.5000002.301755e-090.6200000.5002720.02448.02262.0145289.00.2218780.232256
42262.01451.5000002.301755e-090.6200000.5002720.02448.02362.0152100.00.0789430.210571
52362.01521.9047624.719506e-100.4477050.4152618.02356.22427.016065.00.0535250.176536
\n", + "
" + ], + "text/plain": [ + " top topage k_cond rhp phi decay solidus liquidus \\\n", + "0 152.0 0 1.500000 2.301755e-09 0.620000 0.500 2720.0 2448.0 \n", + "1 810.0 20 1.538462 2.079433e-09 0.599730 0.490 2708.0 2437.2 \n", + "2 1608.0 66 1.500000 2.301755e-09 0.200000 0.500 2720.0 2448.0 \n", + "3 1973.0 100 1.500000 2.301755e-09 0.620000 0.500 2720.0 2448.0 \n", + "4 2262.0 145 1.500000 2.301755e-09 0.620000 0.500 2720.0 2448.0 \n", + "5 2362.0 152 1.904762 4.719506e-10 0.447705 0.415 2618.0 2356.2 \n", + "\n", + " base baseage thickness grain_thickness phi_mean \n", + "0 810.0 20 658.0 0.310357 0.528332 \n", + "1 1608.0 66 798.0 0.511062 0.359571 \n", + "2 1973.0 100 365.0 0.332780 0.088275 \n", + "3 2262.0 145 289.0 0.221878 0.232256 \n", + "4 2362.0 152 100.0 0.078943 0.210571 \n", + "5 2427.0 160 65.0 0.053525 0.176536 " + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "node =warmth.single_node()\n", "node.sediments_inputs = node_template\n", @@ -73,7 +241,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -83,11 +251,11 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ - "node.shf = 60e-3\n", + "node.crustRHP = 2e-6 \n", "node.qbase = 30e-3\n", "node.rift = np.array([[160,145]])\n", "#node.paleoWD=np.array([200]) # Only for mulit rift" @@ -95,7 +263,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -104,7 +272,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -114,9 +282,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 612 ms, sys: 0 ns, total: 612 ms\n", + "Wall time: 624 ms\n" + ] + } + ], "source": [ "%%time\n", "model.simulator.run(parallel=False)\n" @@ -124,18 +301,48 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "array([1.24])" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "model.builder.nodes[0][0].beta" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/workspaces/warmth/warmth/postprocessing.py:199: RuntimeWarning: invalid value encountered in divide\n", + " v=-1*initial_poro/initial_decay*phi1\n" + ] + }, + { + "data": { + "image/png": "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nOvk5ISqqirU1tbC3Nz8ttfMnz8fGzduREJCAs6cOdPsfGVlJSZOnIiJEyfim2++gbOzMzIyMjBx4kTU1tZqXVtSUgI/P7+7tJqIDImPloiow+rfvz8uX74MX19f+Pv7a72sra1v+z39+vUD0LDOy508/vjjuHjxIkJDQ7V6V5pcvXoVxcXFWLduHUaNGoWgoKBmA32bXLp0CREREa1vHBHpBYMMEXVYzz//PEpKSjBz5kycPXsWKSkp+OOPPzBv3jyoVKrbfo+zszP69++PY8eO3fG+Dg4OyM3NxYEDB2573tvbG+bm5vjoo4+QmpqKXbt24c0332x23fXr15GdnY3x48fr1kAiajMGGSLqsDw8PHD8+HGoVCpMmDABYWFhWLJkCezt7SEW3/nH19///nd88803d723vb39HXt1nJ2dsW3bNnz//fcIDg7GunXr8M477zS77rvvvsOECRPg4+PTuoYRkd6IBEEQjF0EEZE+VVdXo3fv3tixYweGDRtmkPeora1FQEAAvv32W4wYMcIg70FE98YeGSLqdKysrPDVV1/ddeG8tsrIyMD//d//McQQGRl7ZIiIiMhksUeGiIiITBaDDBEREZksBhkiIiIyWQwyREREZLIYZIiIiMhkMcgQERGRyWKQISIiIpPFIENEREQmi0GGiIiITNb/A/DDw1bnn5FcAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "%matplotlib inline\n", "import matplotlib.pyplot as plt\n", @@ -148,9 +355,34 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "594913154d98431994a8c4a3808e006b", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "interactive(children=(IntSlider(value=0, description='age', max=160), Output()), _dom_classes=('widget-interac…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "import ipywidgets as widgets\n", "import matplotlib.pyplot as plt\n", @@ -169,13 +401,38 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "f71f1c3e04b54b47bd8402b597b926d5", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "interactive(children=(IntSlider(value=0, description='age', max=160), Output()), _dom_classes=('widget-interac…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "def plot_cell_properties(age):\n", " t = node.result.effective_conductivity(age)\n", - " plt.plot(t[\"values\"],(t[\"depth\"][1:]+t[\"depth\"][:-1])/2)\n", + " plt.plot(t[\"values\"],t[\"depth\"])\n", " plt.gca().invert_yaxis()\n", " plt.xlabel(\"Conductivity\")\n", " plt.ylabel(\"Depth (m)\")\n", @@ -213,7 +470,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.6" + "version": "3.10.13" }, "mimetype": "text/x-python", "name": "python", diff --git a/docs/notebooks/data/0.gri b/docs/notebooks/data/0.gri index 8e3decd..120ca3b 100644 --- a/docs/notebooks/data/0.gri +++ b/docs/notebooks/data/0.gri @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:45ae0d96a78f558d651e31d256a5528b433911977fc8051081747db0e904b16c -size 1067604 +oid sha256:e1dbabef685ecee0f76fb6debf29aacf515d7d144b1f5417101a0c667fb7a347 +size 1055460 diff --git a/docs/notebooks/data/100.gri b/docs/notebooks/data/100.gri index 241c38d..b899d47 100644 --- a/docs/notebooks/data/100.gri +++ b/docs/notebooks/data/100.gri @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:48847b5895e31516fb0a701f08d2609b43020bdfb55dcbe523866aec5051f487 -size 1067604 +oid sha256:ab8497c995df37fbff147f38ab6148555c6ca9558f0b1350d423e1569329d8d2 +size 1055460 diff --git a/docs/notebooks/data/163.gri b/docs/notebooks/data/163.gri index 9e009c7..8bbc9a4 100644 --- a/docs/notebooks/data/163.gri +++ b/docs/notebooks/data/163.gri @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:849d032dbf1683f99793647e270b9e667019acc1bf716bdea1ce580006461613 -size 1067604 +oid sha256:6b78b916e4599589bd085a626a3607cabe90a5f4bef9fb372ef6fc099dff4f3e +size 1055460 diff --git a/docs/notebooks/data/168.gri b/docs/notebooks/data/168.gri index c682d07..680f5c4 100644 --- a/docs/notebooks/data/168.gri +++ b/docs/notebooks/data/168.gri @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d3430e8e8a338a3c8ddc7576d68906505e3ab506f1afd9da96b23a5eb647c93b -size 1067604 +oid sha256:f34479ac9ca81f1e834a23b654df36c7b9da079515b0081e8d0c14494d235fc9 +size 1055460 diff --git a/docs/notebooks/data/170.gri b/docs/notebooks/data/170.gri index c501ffc..9e6d4aa 100644 --- a/docs/notebooks/data/170.gri +++ b/docs/notebooks/data/170.gri @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d2e2d0411f4d36f1acf2b7a06d397c67d8f345de86ecda0564d68f7dde747a00 -size 1067604 +oid sha256:cdbe42007a8651460c6cb86a7404992dbe1449a0209f4aaca6d5f07a71572bfc +size 1055460 diff --git a/docs/notebooks/data/182.gri b/docs/notebooks/data/182.gri index 35b65ac..cbd108f 100644 --- a/docs/notebooks/data/182.gri +++ b/docs/notebooks/data/182.gri @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8da03388145cd2c58e6f04322a8e7163a8b22134b1b4f7bafd64b1c86a379b1f -size 1067604 +oid sha256:6766ef541daf616c706ed6668f4a7e0024a97f66c7a216ee826f636f8dd8f317 +size 1055460 diff --git a/docs/notebooks/data/66.gri b/docs/notebooks/data/66.gri index 98fbcf4..a075e84 100644 --- a/docs/notebooks/data/66.gri +++ b/docs/notebooks/data/66.gri @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:29df4290186312f55b633755802437e9ff5249774d07227d17636cebddd41d42 -size 1067604 +oid sha256:283d52c0ae510cc18817bedcdeabe8eb25d3c4ae36ee36970007d6f7ebf81f08 +size 1055460 diff --git a/subsheat3D/parallel-gmsh.py b/subsheat3D/__parallel-gmsh.py similarity index 100% rename from subsheat3D/parallel-gmsh.py rename to subsheat3D/__parallel-gmsh.py diff --git a/subsheat3D/fixed_mesh_model.py b/subsheat3D/fixed_mesh_model.py index 753028a..387f16b 100644 --- a/subsheat3D/fixed_mesh_model.py +++ b/subsheat3D/fixed_mesh_model.py @@ -3,7 +3,7 @@ from scipy.interpolate import LinearNDInterpolator from warmth.logging import logger -from subsheat3D.Helpers import NodeGrid, NodeParameters1D, top_crust,top_sed,thick_crust, thick_lith, top_lith, top_asth, top_sed_id, bottom_sed_id, thick_sed, volumeOfTet +from subsheat3D.Helpers import NodeParameters1D, top_crust,top_sed,thick_crust, top_lith, top_asth, top_sed_id, bottom_sed_id from subsheat3D.resqpy_helpers import write_tetra_grid_with_properties, write_hexa_grid_with_properties # diff --git a/subsheat3D/parallel-1Dsed.py b/subsheat3D/parallel-1Dsed.py index fb7159a..226b240 100644 --- a/subsheat3D/parallel-1Dsed.py +++ b/subsheat3D/parallel-1Dsed.py @@ -38,7 +38,7 @@ def run(self): i.rift = np.array([[baseage, int(node.sediments["baseage"].iloc[-2])]]) model.parameters.experimental = True if (full_beta_solve): - model.simulator.run_all() + model.simulator.run() else: # # compute only the sedimentation for this node, the crustal thickness and subsidence have to be interpolated @@ -96,13 +96,13 @@ def run_pool(self): print("Process finished: ", result) gridFiles = [] - gridFiles.append(("data/mapA/0.gri",0)) - gridFiles.append(("data/mapA/66.gri",66)) - gridFiles.append(("data/mapA/100.gri",100)) - gridFiles.append(("data/mapA/163.gri",163)) - gridFiles.append(("data/mapA/168.gri",168)) - gridFiles.append(("data/mapA/170.gri",170)) - gridFiles.append(("data/mapA/182.gri",182)) + gridFiles.append(("docs/notebooks/data/0.gri",0)) + gridFiles.append(("docs/notebooks/data/66.gri",66)) + gridFiles.append(("docs/notebooks/data/100.gri",100)) + gridFiles.append(("docs/notebooks/data/163.gri",163)) + gridFiles.append(("docs/notebooks/data/168.gri",168)) + gridFiles.append(("docs/notebooks/data/170.gri",170)) + gridFiles.append(("docs/notebooks/data/182.gri",182)) sedstack = SedimentStack() sedstack.loadFromGridFiles(gridFiles) sedstack.nodeDirectoryPrefix = "nodes-mapA/" diff --git a/tests/__test_dol.py b/tests/__test_dol.py new file mode 100644 index 0000000..015a761 --- /dev/null +++ b/tests/__test_dol.py @@ -0,0 +1,322 @@ +# Copyright (C) 2019 Chris Richardson +# +# This file is part of DOLFINx (https://www.fenicsproject.org) +# +# SPDX-License-Identifier: LGPL-3.0-or-later +"""Unit tests for assembly over domains""" + +from mpi4py import MPI + +import numpy as np +import pytest + +import ufl +from dolfinx import cpp as _cpp +from dolfinx import default_scalar_type, fem, la +from dolfinx.fem import (Constant, Function, assemble_scalar, dirichletbc, + form, functionspace) +from dolfinx.mesh import (GhostMode, Mesh, create_unit_square, locate_entities, + locate_entities_boundary, meshtags, + meshtags_from_entities) + + +@pytest.fixture +def mesh(): + return create_unit_square(MPI.COMM_WORLD, 10, 10) + + +def create_cell_meshtags_from_entities(mesh: Mesh, dim: int, cells: np.ndarray, values: np.ndarray): + mesh.topology.create_connectivity(mesh.topology.dim, 0) + cell_to_vertices = mesh.topology.connectivity(mesh.topology.dim, 0) + entities = _cpp.graph.AdjacencyList_int32([cell_to_vertices.links(cell) for cell in cells]) + return meshtags_from_entities(mesh, dim, entities, values) + + +parametrize_ghost_mode = pytest.mark.parametrize("mode", [ + pytest.param(GhostMode.none, marks=pytest.mark.skipif(condition=MPI.COMM_WORLD.size > 1, + reason="Unghosted interior facets fail in parallel")), + pytest.param(GhostMode.shared_facet, marks=pytest.mark.skipif(condition=MPI.COMM_WORLD.size == 1, + reason="Shared ghost modes fail in serial"))]) + + +@pytest.mark.parametrize("mode", [GhostMode.none, GhostMode.shared_facet]) +@pytest.mark.parametrize("meshtags_factory", [meshtags, create_cell_meshtags_from_entities]) +def test_assembly_dx_domains(mode, meshtags_factory): + mesh = create_unit_square(MPI.COMM_WORLD, 10, 10, ghost_mode=mode) + V = functionspace(mesh, ("Lagrange", 1)) + u, v = ufl.TrialFunction(V), ufl.TestFunction(V) + + # Prepare a marking structures + # indices cover all cells + # values are [1, 2, 3, 3, ...] + cell_map = mesh.topology.index_map(mesh.topology.dim) + num_cells = cell_map.size_local + cell_map.num_ghosts + indices = np.arange(0, num_cells) + values = np.full(indices.shape, 3, dtype=np.intc) + values[0] = 1 + values[1] = 2 + marker = meshtags_factory(mesh, mesh.topology.dim, indices, values) + dx = ufl.Measure('dx', subdomain_data=marker, domain=mesh) + w = Function(V) + w.x.array[:] = 0.5 + + # Assemble matrix + a = form(w * ufl.inner(u, v) * (dx(1) + dx(2) + dx(3))) + A = fem.assemble_matrix(a) + A.scatter_reverse() + a2 = form(w * ufl.inner(u, v) * dx) + A2 = fem.assemble_matrix(a2) + A2.scatter_reverse() + assert np.allclose(A.data, A2.data) + + bc = dirichletbc(Function(V), range(30)) + + # Assemble vector + L = form(ufl.inner(w, v) * (dx(1) + dx(2) + dx(3))) + b = fem.assemble_vector(L) + + fem.apply_lifting(b.array, [a], [[bc]]) + b.scatter_reverse(la.InsertMode.add) + fem.set_bc(b.array, [bc]) + + L2 = form(ufl.inner(w, v) * dx) + b2 = fem.assemble_vector(L2) + fem.apply_lifting(b2.array, [a], [[bc]]) + b2.scatter_reverse(la.InsertMode.add) + fem.set_bc(b2.array, [bc]) + assert np.allclose(b.array, b2.array) + + # Assemble scalar + L = form(w * (dx(1) + dx(2) + dx(3))) + s = assemble_scalar(L) + s = mesh.comm.allreduce(s, op=MPI.SUM) + assert s == pytest.approx(0.5, rel=1.0e-6) + L2 = form(w * dx) + s2 = assemble_scalar(L2) + s2 = mesh.comm.allreduce(s2, op=MPI.SUM) + assert s == pytest.approx(s2, rel=1.0e-6) + + +@pytest.mark.parametrize("mode", [GhostMode.none, GhostMode.shared_facet]) +def test_assembly_ds_domains(mode): + mesh = create_unit_square(MPI.COMM_WORLD, 10, 10, ghost_mode=mode) + V = functionspace(mesh, ("Lagrange", 1)) + u, v = ufl.TrialFunction(V), ufl.TestFunction(V) + + def bottom(x): + return np.isclose(x[1], 0.0) + + def top(x): + return np.isclose(x[1], 1.0) + + def left(x): + return np.isclose(x[0], 0.0) + + def right(x): + return np.isclose(x[0], 1.0) + + bottom_facets = locate_entities_boundary(mesh, mesh.topology.dim - 1, bottom) + bottom_vals = np.full(bottom_facets.shape, 1, np.intc) + + top_facets = locate_entities_boundary(mesh, mesh.topology.dim - 1, top) + top_vals = np.full(top_facets.shape, 2, np.intc) + + left_facets = locate_entities_boundary(mesh, mesh.topology.dim - 1, left) + left_vals = np.full(left_facets.shape, 3, np.intc) + + right_facets = locate_entities_boundary(mesh, mesh.topology.dim - 1, right) + right_vals = np.full(right_facets.shape, 6, np.intc) + + indices = np.hstack((bottom_facets, top_facets, left_facets, right_facets)) + values = np.hstack((bottom_vals, top_vals, left_vals, right_vals)) + + indices, pos = np.unique(indices, return_index=True) + marker = meshtags(mesh, mesh.topology.dim - 1, indices, values[pos]) + + ds = ufl.Measure('ds', subdomain_data=marker, domain=mesh) + + w = Function(V) + w.x.array[:] = 0.5 + + bc = dirichletbc(Function(V), range(30)) + + # Assemble matrix + a = form(w * ufl.inner(u, v) * (ds(1) + ds(2) + ds(3) + ds(6))) + A = fem.assemble_matrix(a) + A.scatter_reverse() + a2 = form(w * ufl.inner(u, v) * ds) + A2 = fem.assemble_matrix(a2) + A2.scatter_reverse() + assert np.allclose(A.data, A2.data) + + # Assemble vector + L = form(ufl.inner(w, v) * (ds(1) + ds(2) + ds(3) + ds(6))) + b = fem.assemble_vector(L) + + fem.apply_lifting(b.array, [a], [[bc]]) + b.scatter_reverse(la.InsertMode.add) + fem.set_bc(b.array, [bc]) + + L2 = form(ufl.inner(w, v) * ds) + b2 = fem.assemble_vector(L2) + fem.apply_lifting(b2.array, [a2], [[bc]]) + b2.scatter_reverse(la.InsertMode.add) + fem.set_bc(b2.array, [bc]) + assert np.allclose(b.array, b2.array) + + # Assemble scalar + L = form(w * (ds(1) + ds(2) + ds(3) + ds(6))) + s = assemble_scalar(L) + s = mesh.comm.allreduce(s, op=MPI.SUM) + L2 = form(w * ds) + s2 = assemble_scalar(L2) + s2 = mesh.comm.allreduce(s2, op=MPI.SUM) + assert s == pytest.approx(s2, 1.0e-6) + assert 2.0 == pytest.approx(s, 1.0e-6) # /NOSONAR + + +@parametrize_ghost_mode +def test_assembly_dS_domains(mode): + N = 10 + mesh = create_unit_square(MPI.COMM_WORLD, N, N, ghost_mode=mode) + one = Constant(mesh, default_scalar_type(1)) + val = assemble_scalar(form(one * ufl.dS)) + val = mesh.comm.allreduce(val, op=MPI.SUM) + assert val == pytest.approx(2 * (N - 1) + N * np.sqrt(2), 1.0e-5) + + +@parametrize_ghost_mode +def test_additivity(mode): + mesh = create_unit_square(MPI.COMM_WORLD, 12, 12, ghost_mode=mode) + V = functionspace(mesh, ("Lagrange", 1)) + + f1 = Function(V) + f2 = Function(V) + f3 = Function(V) + f1.x.array[:] = 1.0 + f2.x.array[:] = 2.0 + f3.x.array[:] = 3.0 + j1 = ufl.inner(f1, f1) * ufl.dx(mesh) + j2 = ufl.inner(f2, f2) * ufl.ds(mesh) + j3 = ufl.inner(ufl.avg(f3), ufl.avg(f3)) * ufl.dS(mesh) + + # Assemble each scalar form separately + J1 = mesh.comm.allreduce(assemble_scalar(form(j1)), op=MPI.SUM) + J2 = mesh.comm.allreduce(assemble_scalar(form(j2)), op=MPI.SUM) + J3 = mesh.comm.allreduce(assemble_scalar(form(j3)), op=MPI.SUM) + + # Sum forms and assemble the result + J12 = mesh.comm.allreduce(assemble_scalar(form(j1 + j2)), op=MPI.SUM) + J13 = mesh.comm.allreduce(assemble_scalar(form(j1 + j3)), op=MPI.SUM) + J23 = mesh.comm.allreduce(assemble_scalar(form(j2 + j3)), op=MPI.SUM) + J123 = mesh.comm.allreduce(assemble_scalar(form(j1 + j2 + j3)), op=MPI.SUM) + + # Compare assembled values + assert (J1 + J2) == pytest.approx(J12) + assert (J1 + J3) == pytest.approx(J13) + assert (J2 + J3) == pytest.approx(J23) + assert (J1 + J2 + J3) == pytest.approx(J123) + + +def test_manual_integration_domains(): + """Test that specifying integration domains manually i.e. + by passing a list of cell indices or (cell, local facet) pairs to + form gives the same result as the usual approach of tagging""" + n = 4 + msh = create_unit_square(MPI.COMM_WORLD, n, n) + + V = functionspace(msh, ("Lagrange", 1)) + u = ufl.TrialFunction(V) + v = ufl.TestFunction(V) + + # Create meshtags to mark some cells + tdim = msh.topology.dim + cell_map = msh.topology.index_map(tdim) + num_cells = cell_map.size_local + cell_map.num_ghosts + cell_indices = np.arange(0, num_cells) + cell_values = np.zeros_like(cell_indices, dtype=np.intc) + marked_cells = locate_entities(msh, tdim, lambda x: x[0] < 0.75) + cell_values[marked_cells] = 7 + mt_cells = meshtags(msh, tdim, cell_indices, cell_values) + + # Create meshtags to mark some exterior facets + msh.topology.create_entities(tdim - 1) + facet_map = msh.topology.index_map(tdim - 1) + num_facets = facet_map.size_local + facet_map.num_ghosts + facet_indices = np.arange(0, num_facets) + facet_values = np.zeros_like(facet_indices, dtype=np.intc) + marked_ext_facets = locate_entities_boundary(msh, tdim - 1, lambda x: np.isclose(x[0], 0.0)) + marked_int_facets = locate_entities(msh, tdim - 1, lambda x: x[0] < 0.75) + # marked_int_facets will also contain facets on the boundary, so set + # these values first, followed by the values for marked_ext_facets + facet_values[marked_int_facets] = 3 + facet_values[marked_ext_facets] = 6 + mt_facets = meshtags(msh, tdim - 1, facet_indices, facet_values) + + # Create measures + dx_mt = ufl.Measure("dx", subdomain_data=mt_cells, domain=msh) + ds_mt = ufl.Measure("ds", subdomain_data=mt_facets, domain=msh) + dS_mt = ufl.Measure("dS", subdomain_data=mt_facets, domain=msh) + + g = Function(V) + g.interpolate(lambda x: x[1]**2) + + def create_forms(dx, ds, dS): + a = form(ufl.inner(g * u, v) * (dx(0) + dx(7) + ds(6)) + ufl.inner(g * u("+"), v("+") + v("-")) * dS(3)) + L = form(ufl.inner(g, v) * (dx(0) + dx(7) + ds(6)) + ufl.inner(g, v("+") + v("-")) * dS(3)) + return (a, L) + + # Create forms and assemble + a, L = create_forms(dx_mt, ds_mt, dS_mt) + A_mt = fem.assemble_matrix(a) + A_mt.scatter_reverse() + b_mt = fem.assemble_vector(L) + + # Manually specify cells to integrate over (removing ghosts + # to give same result as above) + cell_domains = [(domain_id, cell_indices[(cell_values == domain_id) + & (cell_indices < cell_map.size_local)]) + for domain_id in [0, 7]] + + # Manually specify exterior facets to integrate over as + # (cell, local facet) pairs + ext_facet_domain = [] + msh.topology.create_connectivity(tdim, tdim - 1) + msh.topology.create_connectivity(tdim - 1, tdim) + c_to_f = msh.topology.connectivity(tdim, tdim - 1) + f_to_c = msh.topology.connectivity(tdim - 1, tdim) + for f in marked_ext_facets: + if f < facet_map.size_local: + c = f_to_c.links(f)[0] + local_f = np.where(c_to_f.links(c) == f)[0][0] + ext_facet_domain.append(c) + ext_facet_domain.append(local_f) + ext_facet_domains = [(6, ext_facet_domain)] + + # Manually specify interior facets to integrate over + int_facet_domain = [] + for f in marked_int_facets: + if f >= facet_map.size_local or len(f_to_c.links(f)) != 2: + continue + c_0, c_1 = f_to_c.links(f)[0], f_to_c.links(f)[1] + local_f_0 = np.where(c_to_f.links(c_0) == f)[0][0] + local_f_1 = np.where(c_to_f.links(c_1) == f)[0][0] + int_facet_domain.append(c_0) + int_facet_domain.append(local_f_0) + int_facet_domain.append(c_1) + int_facet_domain.append(local_f_1) + int_facet_domains = [(3, int_facet_domain)] + + # Create measures + dx_manual = ufl.Measure("dx", subdomain_data=cell_domains, domain=msh) + ds_manual = ufl.Measure("ds", subdomain_data=ext_facet_domains, domain=msh) + dS_manual = ufl.Measure("dS", subdomain_data=int_facet_domains, domain=msh) + + # Assemble forms and check + a, L = create_forms(dx_manual, ds_manual, dS_manual) + A = fem.assemble_matrix(a) + A.scatter_reverse() + b = fem.assemble_vector(L) + + assert np.allclose(A.data, A_mt.data) + assert np.allclose(b.array, b_mt.array) \ No newline at end of file diff --git a/tests/temperer3D_mapA_example.py b/tests/temperer3D_mapA_example.py index 878f074..9609609 100644 --- a/tests/temperer3D_mapA_example.py +++ b/tests/temperer3D_mapA_example.py @@ -194,7 +194,7 @@ def run( nodeGrid, run_simulation=True, start_time=182, end_time=0, out_dir = "o # # NOTE: to compute the required 1D node solutions, you must first run subsheat3D/parallel-1Dsed.py using the same NodeGrid parameters as below! # - +ng = NodeGrid(150, 0, 485, 548, 500, 1000, 5, 1000, 1000) ng = NodeGrid(0, 0, 11, 11, 500, 1000, 5, 100, 100) # ng = NodeGrid(25400, 41600, 31, 31, 100, 100, 2, 100, 100) diff --git a/tests/test_from_grid.py b/tests/test_from_grid.py index 91c2bc6..bf88e9a 100644 --- a/tests/test_from_grid.py +++ b/tests/test_from_grid.py @@ -5,256 +5,256 @@ import sys import numpy as np -import temperer - - -maps_dir = Path("../data/mapA") - -model = temperer.Model() - -inputs = model.builder.input_horizons_template - -inputs.loc[0]=[0,"0.gri",None,"Onlap"] -inputs.loc[1]=[66,"66.gri",None,"Onlap"] -inputs.loc[2]=[100,"100.gri",None,"Onlap"] -inputs.loc[3]=[163,"163.gri",None,"Erosive"] -inputs.loc[4]=[168,"168.gri",None,"Erosive"] -inputs.loc[5]=[170,"170.gri",None,"Onlap"] -inputs.loc[6]=[182,"182.gri",None,"Erosive"] -model.builder.input_horizons=inputs - - -inc = 2000 -model.builder.define_geometry(maps_dir/"0.gri",xinc=inc,yinc=inc,fformat="irap_binary") - -model.builder.extract_nodes(4,maps_dir) - -from temperer.data import haq87 -model.builder.set_eustatic_sea_level(haq87) - -for i in model.builder.iter_node(): - i.rift=[[182,175]] - - -# simulate every 10 nodes -for index in model.builder.grid.indexing_arr: - if (index[0] % 10 > 0): - pass - else: - if isinstance(model.builder.nodes[index[0]][index[1]],bool) is False: - model.builder.nodes[index[0]][index[1]] = False - if (index[1] % 10 > 0): - pass - else: - if isinstance(model.builder.nodes[index[0]][index[1]],bool) is False: - model.builder.nodes[index[0]][index[1]] = False - -indexer_full_sim = [i.indexer for i in model.builder.iter_node() if i._full_simulation is True] - -for ni in range(len(model.builder.nodes)): - for nj in range(len(model.builder.nodes[ni])): - if model.builder.nodes[ni][nj] is not False: - nn = model.builder.nodes[ni][nj] - if nn.Y>40000: - nn.sediments['phi'] = np.ones([len(nn.sediments['phi']),1]) * 0.5 - # nn.sediments['k_cond'] = np.ones([len(nn.sediments['k_cond']),1]) * 2.2 - - -model.simulator.run(save=False,purge=True) - -# %% -for i in model.builder.iter_node(): - if i is not False: - print(i.result.heatflow(0)) - -# interpolate and extrapolate the missing nodes -# find nearby nodes from the array indexer_full_sim, which is sorted by x-index -import itertools -for ni in range(len(model.builder.nodes)): - for nj in range(len(model.builder.nodes[ni])): - if model.builder.nodes[ni][nj] is False: - closest_x_up = [] - for j in range(ni,len(model.builder.nodes)): - matching_x = [ i[0] for i in indexer_full_sim if i[0]==j ] - closest_x_up = closest_x_up + list(set(matching_x)) - if len(matching_x)>0: - break - closest_x_down = [] - for j in range(ni-1,-1,-1): - matching_x = [ i[0] for i in indexer_full_sim if i[0]==j ] - closest_x_down = closest_x_down + list(set(matching_x)) - if len(matching_x)>0: - break - closest_y_up = [] - for j in range(nj,len(model.builder.nodes[ni])): - matching_y = [ i[1] for i in indexer_full_sim if (i[1]==j and ((i[0] in closest_x_up) or i[0] in closest_x_down)) ] - closest_y_up = closest_y_up + list(set(matching_y)) - if len(matching_y)>0: - break - closest_y_down = [] - for j in range(nj-1,-1,-1): - matching_y = [ i[1] for i in indexer_full_sim if (i[1]==j and (i[0] in closest_x_up or i[0] in closest_x_down) ) ] - closest_y_down = closest_y_down + list(set(matching_y)) - if len(matching_y)>0: - break - - interpolationNodes = [ model.builder.nodes[i[0]][i[1]] for i in itertools.product(closest_x_up+closest_x_down, closest_y_up+closest_y_down) ] - interpolationNodes = [nn for nn in interpolationNodes if nn is not False] - node = temperer.build.interpolateNode(interpolationNodes) - node.X, node.Y = model.builder.grid.location_grid[ni,nj,:] - model.builder.nodes[ni][nj] = node - else: - node = temperer.build.interpolateNode([model.builder.nodes[ni][nj]]) # "interpolate" the node from itself to make sure the same member variables exist at the end - model.builder.nodes[ni][nj] = node - -max_age = model.builder.input_horizons['Age'].to_list()[-1] - -# -# assert that the grid of nodes is fully defined; -# assert that the .crust_ls property has been defined for every node -# -for index in model.builder.grid.indexing_arr: - nn = model.builder.nodes[index[0]][index[1]] - assert nn is not False - assert type(nn)==temperer.build.single_node - assert nn.crust_ls.shape[0] > max_age-1 - if nn.Y>40000: - nn.sediments['phi'] = np.ones([len(nn.sediments['phi']),1]) * 0.50 - # nn.sediments['k_cond'] = np.ones([len(nn.sediments['k_cond']),1]) * 2.2 - -# -# run the 3D simulation -# -from subsheat3D.fixed_mesh_model import UniformNodeGridFixedSizeMeshModel -from subsheat3D.Helpers import NodeGrid - -try: - os.mkdir('out') -except FileExistsError: - pass -try: - os.mkdir('mesh') -except FileExistsError: - pass -try: - os.mkdir('temp') -except FileExistsError: - pass - -# convert to 1D array of nodes and add padding! - -pad = 1 -nodes = [] -for j in range(-pad, model.builder.grid.num_nodes_y+pad): - for i in range(-pad, model.builder.grid.num_nodes_x+pad): - node_i = max(min(i, model.builder.grid.num_nodes_x-1), 0) - node_j = max(min(j, model.builder.grid.num_nodes_y-1), 0) - if (node_i != i) or (node_j != j): - # this is a padded node: create it from the nearest node in the unpadded grid - node_new = temperer.build.interpolateNode([model.builder.nodes[node_j][node_i]]) - node_new.X = model.builder.grid.origin_x + i * model.builder.grid.step_x - node_new.Y = model.builder.grid.origin_y + j * model.builder.grid.step_y - nodes.append(node_new) - else: - nodes.append(model.builder.nodes[node_j][node_i]) - -nodeGrid = NodeGrid(0, 0, model.builder.grid.num_nodes_x+2*pad, model.builder.grid.num_nodes_y+2*pad, 100, 100, 5, 100, 100) -nodeGrid.num_nodes_x = model.builder.grid.num_nodes_x + 2*pad -nodeGrid.num_nodes_y = model.builder.grid.num_nodes_y + 2*pad - - -start_time, end_time = max_age, 0 -modelNamePrefix = 'test1_' -mms2, mms_tti = [], [] -out_dir = './out/' - -no_steps = 4 -dt = 314712e8 / no_steps - -for tti in range(start_time, end_time-1,-1): - rebuild_mesh = (tti==start_time) +# import warmth + + +# maps_dir = Path("../data/mapA") + +# model = warmth.Model() + +# inputs = model.builder.input_horizons_template + +# inputs.loc[0]=[0,"0.gri",None,"Onlap"] +# inputs.loc[1]=[66,"66.gri",None,"Onlap"] +# inputs.loc[2]=[100,"100.gri",None,"Onlap"] +# inputs.loc[3]=[163,"163.gri",None,"Erosive"] +# inputs.loc[4]=[168,"168.gri",None,"Erosive"] +# inputs.loc[5]=[170,"170.gri",None,"Onlap"] +# inputs.loc[6]=[182,"182.gri",None,"Erosive"] +# model.builder.input_horizons=inputs + + +# inc = 2000 +# model.builder.define_geometry(maps_dir/"0.gri",xinc=inc,yinc=inc,fformat="irap_binary") + +# model.builder.extract_nodes(4,maps_dir) + +# from warmth.data import haq87 +# model.builder.set_eustatic_sea_level(haq87) + +# for i in model.builder.iter_node(): +# i.rift=[[182,175]] + + +# # simulate every 10 nodes +# for index in model.builder.grid.indexing_arr: +# if (index[0] % 10 > 0): +# pass +# else: +# if isinstance(model.builder.nodes[index[0]][index[1]],bool) is False: +# model.builder.nodes[index[0]][index[1]] = False +# if (index[1] % 10 > 0): +# pass +# else: +# if isinstance(model.builder.nodes[index[0]][index[1]],bool) is False: +# model.builder.nodes[index[0]][index[1]] = False + +# indexer_full_sim = [i.indexer for i in model.builder.iter_node() if i._full_simulation is True] + +# for ni in range(len(model.builder.nodes)): +# for nj in range(len(model.builder.nodes[ni])): +# if model.builder.nodes[ni][nj] is not False: +# nn = model.builder.nodes[ni][nj] +# if nn.Y>40000: +# nn.sediments['phi'] = np.ones([len(nn.sediments['phi']),1]) * 0.5 +# # nn.sediments['k_cond'] = np.ones([len(nn.sediments['k_cond']),1]) * 2.2 + + +# model.simulator.run(save=False,purge=True) + +# # %% +# for i in model.builder.iter_node(): +# if i is not False: +# print(i.result.heatflow(0)) + +# # interpolate and extrapolate the missing nodes +# # find nearby nodes from the array indexer_full_sim, which is sorted by x-index +# import itertools +# for ni in range(len(model.builder.nodes)): +# for nj in range(len(model.builder.nodes[ni])): +# if model.builder.nodes[ni][nj] is False: +# closest_x_up = [] +# for j in range(ni,len(model.builder.nodes)): +# matching_x = [ i[0] for i in indexer_full_sim if i[0]==j ] +# closest_x_up = closest_x_up + list(set(matching_x)) +# if len(matching_x)>0: +# break +# closest_x_down = [] +# for j in range(ni-1,-1,-1): +# matching_x = [ i[0] for i in indexer_full_sim if i[0]==j ] +# closest_x_down = closest_x_down + list(set(matching_x)) +# if len(matching_x)>0: +# break +# closest_y_up = [] +# for j in range(nj,len(model.builder.nodes[ni])): +# matching_y = [ i[1] for i in indexer_full_sim if (i[1]==j and ((i[0] in closest_x_up) or i[0] in closest_x_down)) ] +# closest_y_up = closest_y_up + list(set(matching_y)) +# if len(matching_y)>0: +# break +# closest_y_down = [] +# for j in range(nj-1,-1,-1): +# matching_y = [ i[1] for i in indexer_full_sim if (i[1]==j and (i[0] in closest_x_up or i[0] in closest_x_down) ) ] +# closest_y_down = closest_y_down + list(set(matching_y)) +# if len(matching_y)>0: +# break + +# interpolationNodes = [ model.builder.nodes[i[0]][i[1]] for i in itertools.product(closest_x_up+closest_x_down, closest_y_up+closest_y_down) ] +# interpolationNodes = [nn for nn in interpolationNodes if nn is not False] +# node = warmth.build.interpolateNode(interpolationNodes) +# node.X, node.Y = model.builder.grid.location_grid[ni,nj,:] +# model.builder.nodes[ni][nj] = node +# else: +# node = warmth.build.interpolateNode([model.builder.nodes[ni][nj]]) # "interpolate" the node from itself to make sure the same member variables exist at the end +# model.builder.nodes[ni][nj] = node + +# max_age = model.builder.input_horizons['Age'].to_list()[-1] + +# # +# # assert that the grid of nodes is fully defined; +# # assert that the .crust_ls property has been defined for every node +# # +# for index in model.builder.grid.indexing_arr: +# nn = model.builder.nodes[index[0]][index[1]] +# assert nn is not False +# assert type(nn)==warmth.build.single_node +# assert nn.crust_ls.shape[0] > max_age-1 +# if nn.Y>40000: +# nn.sediments['phi'] = np.ones([len(nn.sediments['phi']),1]) * 0.50 +# # nn.sediments['k_cond'] = np.ones([len(nn.sediments['k_cond']),1]) * 2.2 + +# # +# # run the 3D simulation +# # +# from subsheat3D.fixed_mesh_model import UniformNodeGridFixedSizeMeshModel +# from subsheat3D.Helpers import NodeGrid + +# try: +# os.mkdir('out') +# except FileExistsError: +# pass +# try: +# os.mkdir('mesh') +# except FileExistsError: +# pass +# try: +# os.mkdir('temp') +# except FileExistsError: +# pass + +# # convert to 1D array of nodes and add padding! + +# pad = 1 +# nodes = [] +# for j in range(-pad, model.builder.grid.num_nodes_y+pad): +# for i in range(-pad, model.builder.grid.num_nodes_x+pad): +# node_i = max(min(i, model.builder.grid.num_nodes_x-1), 0) +# node_j = max(min(j, model.builder.grid.num_nodes_y-1), 0) +# if (node_i != i) or (node_j != j): +# # this is a padded node: create it from the nearest node in the unpadded grid +# node_new = warmth.build.interpolateNode([model.builder.nodes[node_j][node_i]]) +# node_new.X = model.builder.grid.origin_x + i * model.builder.grid.step_x +# node_new.Y = model.builder.grid.origin_y + j * model.builder.grid.step_y +# nodes.append(node_new) +# else: +# nodes.append(model.builder.nodes[node_j][node_i]) + +# nodeGrid = NodeGrid(0, 0, model.builder.grid.num_nodes_x+2*pad, model.builder.grid.num_nodes_y+2*pad, 100, 100, 5, 100, 100) +# nodeGrid.num_nodes_x = model.builder.grid.num_nodes_x + 2*pad +# nodeGrid.num_nodes_y = model.builder.grid.num_nodes_y + 2*pad + + +# start_time, end_time = max_age, 0 +# modelNamePrefix = 'test1_' +# mms2, mms_tti = [], [] +# out_dir = './out/' + +# no_steps = 4 +# dt = 314712e8 / no_steps + +# for tti in range(start_time, end_time-1,-1): +# rebuild_mesh = (tti==start_time) - # build_tti = 0 # use the full stack at every time step - build_tti = tti # do sedimentation: update stack positions at time steps - - if rebuild_mesh: - print("Rebuild/reload mesh at tti=", tti) - mm2 = UniformNodeGridFixedSizeMeshModel(nodeGrid, nodes, modelName=modelNamePrefix+str(tti), sedimentsOnly=False) - mm2.buildMesh(build_tti) - else: - print("Re-generating mesh vertices at tti=", tti) - mm2.updateMesh(build_tti) - - mm2.baseFluxMagnitude = 0.04 if (tti>50) else 0.04 - print("===",tti," ",mm2.baseFluxMagnitude,"=========== ") - if ( len(mms2) == 0): - mm2.setupSolverAndSolve(no_steps=no_steps, time_step = dt, skip_setup=False) - else: - mm2.setupSolverAndSolve( no_steps=no_steps, time_step=dt, skip_setup=(not rebuild_mesh)) - if (True): - mm2.writeLayerIDFunction(out_dir+"LayerID-"+str(tti)+".xdmf", tti=tti) - mm2.writeTemperatureFunction(out_dir+"Temperature-"+str(tti)+".xdmf", tti=tti) - mm2.writePoroFunction(out_dir+"Poro0-"+str(tti)+".xdmf", tti=tti) - if (tti==0): - fn = out_dir+"mesh-pos-"+str(tti)+".npy" - np.save(fn, mm2.mesh.geometry.x) - print("np save", fn, mm2.mesh.geometry.x.shape) - fn = out_dir+"T-value-"+str(tti)+".npy" - np.save(fn, mm2.uh.x.array[:]) - fn = out_dir+"cell-pos-"+str(tti)+".npy" - print("cell pos shape", mm2.getCellMidpoints().shape ) - np.save(fn, mm2.getCellMidpoints()) - print("thermal cond shape", mm2.thermalCond.x.array.shape) - fn = out_dir+"k-value-"+str(tti)+".npy" - np.save(fn, mm2.thermalCond.x.array[:]) - fn = out_dir+"phi-value-"+str(tti)+".npy" - np.save(fn, mm2.mean_porosity.x.array[:]) - - mms2.append(mm2) - mms_tti.append(tti) - -EPCfilename = mm2.write_hexa_mesh_resqml("temp/") -print("RESQML model written to: " , EPCfilename) -# read_mesh_resqml(EPCfilename, meshTitle="hexamesh") # test reading of the .epc file - - -# hx = model.builder.grid.num_nodes_x // 2 -# hy = model.builder.grid.num_nodes_y // 2 -hx = model.builder.grid.num_nodes_x - 1 - pad -hy = model.builder.grid.num_nodes_y - 1 - pad -# hx = 1 -# hy = 1 - -nn = model.builder.nodes[hy][hx] -dd = nn.depth_out[:,0] - -node_ind = hy*model.builder.grid.num_nodes_x + hx -v_per_n = int(mm2.mesh_vertices.shape[0]/(model.builder.grid.num_nodes_y*model.builder.grid.num_nodes_x)) - -temp_1d = np.nan_to_num(nn.temperature_out[:,0], nan=5.0) -temp_3d_ind = np.array([ np.where([mm2.mesh_reindex==i])[1][0] for i in range(node_ind*v_per_n, (node_ind+1)*v_per_n) ] ) -dd_mesh = mm2.mesh.geometry.x[temp_3d_ind,2] -temp_3d_mesh = mm2.u_n.x.array[temp_3d_ind] - -temp_1d_at_mesh_pos = np.interp(dd_mesh, dd, temp_1d) -dd_subset = np.where(dd_mesh<5000) -print(f'Max. absolute error in temperature at 3D mesh vertex positions: {np.amax(np.abs(temp_1d_at_mesh_pos-temp_3d_mesh))}') -print(f'Max. absolute error at depths < 5000m: {np.amax(np.abs(temp_1d_at_mesh_pos[dd_subset]-temp_3d_mesh[dd_subset]))}') - - -# when not in ipython: -# import matplotlib as plt -# plt.use('qtagg') -# -# %matplotlib -# import matplotlib as plt -# plt.use('TkAgg') - -# plt.pyplot.plot( dd, temp_1d, label=f'1D simulation'); -# # plt.pyplot.plot( dd, temp_3d, label=f'3D simulation'); -# plt.pyplot.plot( dd_mesh, temp_3d_mesh, 'o', label=f'3D simulation (nodes)'); -# plt.pyplot.legend(loc="lower right", prop={'size': 7}) -# plt.pyplot.show() +# # build_tti = 0 # use the full stack at every time step +# build_tti = tti # do sedimentation: update stack positions at time steps + +# if rebuild_mesh: +# print("Rebuild/reload mesh at tti=", tti) +# mm2 = UniformNodeGridFixedSizeMeshModel(nodeGrid, nodes, modelName=modelNamePrefix+str(tti), sedimentsOnly=False) +# mm2.buildMesh(build_tti) +# else: +# print("Re-generating mesh vertices at tti=", tti) +# mm2.updateMesh(build_tti) + +# mm2.baseFluxMagnitude = 0.04 if (tti>50) else 0.04 +# print("===",tti," ",mm2.baseFluxMagnitude,"=========== ") +# if ( len(mms2) == 0): +# mm2.setupSolverAndSolve(no_steps=no_steps, time_step = dt, skip_setup=False) +# else: +# mm2.setupSolverAndSolve( no_steps=no_steps, time_step=dt, skip_setup=(not rebuild_mesh)) +# if (True): +# mm2.writeLayerIDFunction(out_dir+"LayerID-"+str(tti)+".xdmf", tti=tti) +# mm2.writeTemperatureFunction(out_dir+"Temperature-"+str(tti)+".xdmf", tti=tti) +# mm2.writePoroFunction(out_dir+"Poro0-"+str(tti)+".xdmf", tti=tti) +# if (tti==0): +# fn = out_dir+"mesh-pos-"+str(tti)+".npy" +# np.save(fn, mm2.mesh.geometry.x) +# print("np save", fn, mm2.mesh.geometry.x.shape) +# fn = out_dir+"T-value-"+str(tti)+".npy" +# np.save(fn, mm2.uh.x.array[:]) +# fn = out_dir+"cell-pos-"+str(tti)+".npy" +# print("cell pos shape", mm2.getCellMidpoints().shape ) +# np.save(fn, mm2.getCellMidpoints()) +# print("thermal cond shape", mm2.thermalCond.x.array.shape) +# fn = out_dir+"k-value-"+str(tti)+".npy" +# np.save(fn, mm2.thermalCond.x.array[:]) +# fn = out_dir+"phi-value-"+str(tti)+".npy" +# np.save(fn, mm2.mean_porosity.x.array[:]) + +# mms2.append(mm2) +# mms_tti.append(tti) + +# EPCfilename = mm2.write_hexa_mesh_resqml("temp/") +# print("RESQML model written to: " , EPCfilename) +# # read_mesh_resqml(EPCfilename, meshTitle="hexamesh") # test reading of the .epc file + + +# # hx = model.builder.grid.num_nodes_x // 2 +# # hy = model.builder.grid.num_nodes_y // 2 +# hx = model.builder.grid.num_nodes_x - 1 - pad +# hy = model.builder.grid.num_nodes_y - 1 - pad +# # hx = 1 +# # hy = 1 + +# nn = model.builder.nodes[hy][hx] +# dd = nn.depth_out[:,0] + +# node_ind = hy*model.builder.grid.num_nodes_x + hx +# v_per_n = int(mm2.mesh_vertices.shape[0]/(model.builder.grid.num_nodes_y*model.builder.grid.num_nodes_x)) + +# temp_1d = np.nan_to_num(nn.temperature_out[:,0], nan=5.0) +# temp_3d_ind = np.array([ np.where([mm2.mesh_reindex==i])[1][0] for i in range(node_ind*v_per_n, (node_ind+1)*v_per_n) ] ) +# dd_mesh = mm2.mesh.geometry.x[temp_3d_ind,2] +# temp_3d_mesh = mm2.u_n.x.array[temp_3d_ind] + +# temp_1d_at_mesh_pos = np.interp(dd_mesh, dd, temp_1d) +# dd_subset = np.where(dd_mesh<5000) +# print(f'Max. absolute error in temperature at 3D mesh vertex positions: {np.amax(np.abs(temp_1d_at_mesh_pos-temp_3d_mesh))}') +# print(f'Max. absolute error at depths < 5000m: {np.amax(np.abs(temp_1d_at_mesh_pos[dd_subset]-temp_3d_mesh[dd_subset]))}') + + +# # when not in ipython: +# # import matplotlib as plt +# # plt.use('qtagg') +# # +# # %matplotlib +# # import matplotlib as plt +# # plt.use('TkAgg') + +# # plt.pyplot.plot( dd, temp_1d, label=f'1D simulation'); +# # # plt.pyplot.plot( dd, temp_3d, label=f'3D simulation'); +# # plt.pyplot.plot( dd_mesh, temp_3d_mesh, 'o', label=f'3D simulation (nodes)'); +# # plt.pyplot.legend(loc="lower right", prop={'size': 7}) +# # plt.pyplot.show() diff --git a/tests/test_integration.py b/tests/test_integration.py index 0fbb10b..65ed5f8 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -40,8 +40,8 @@ def test_integration_single_rift(): sed = sediments(model.builder.single_node_sediments_inputs_template) node = warmth.single_node() node.sediments_inputs = sed - node.shf = 60e-3 node.qbase = 30e-3 + node.crustRHP = (60e-3-node.qbase)/node.hc/0.5 node.rift = np.array([[160,145]]) model.parameters.time_start = 160 model.parameters.time_end = 0 @@ -63,8 +63,8 @@ def test_integration_multi_rift(): sed = sediments(model.builder.single_node_sediments_inputs_template) node = warmth.single_node() node.sediments_inputs = sed - node.shf = 60e-3 node.qbase = 30e-3 + node.crustRHP = (60e-3-node.qbase)/node.hc/0.5 node.rift = np.array([[160,145],[100,80]]) node.paleoWD=np.array([200]) model.parameters.time_start = 160 diff --git a/warmth/build.py b/warmth/build.py index 10157ab..da3d636 100644 --- a/warmth/build.py +++ b/warmth/build.py @@ -70,13 +70,13 @@ class single_node: """ def __init__(self): - self.shf: float = 30e-3 self.hc: float = 30e3 self.hLith: float = 130e3 self.kLith: float = 2 self.kCrust: float = 2.5 self.kAsth:float = 100 - self.rhp: float = 2e-6 + self.crustRHP: float = 2e-6 #microW + self._upperCrust_ratio =0.5 self.crustliquid: float = 2500.0 self.crustsolid: float = 2800.0 self.lithliquid: float = 2700.0 @@ -110,6 +110,10 @@ def __init__(self): self._subsidence:np.ndarray[np.float64]|None=None + @property + def shf(self)->float: + return ((self.crustRHP*self._upperCrust_ratio)*self.hc) + self.qbase + @property def result(self)-> Results|None: """Results of 1D simulation @@ -786,7 +790,11 @@ def define_geometry(self, path: Path, xinc: float = None, yinc: float = None, ff xinc = hor.xinc if isinstance(yinc, type(None)): yinc = hor.yinc - self.grid = Grid(hor.xori, hor.yori, hor.ncol, hor.nrow, xinc, yinc) + xmax = hor.xori+(hor.ncol*hor.xinc) + ymax = hor.yori+(hor.nrow*hor.yinc) + new_ncol = math.floor((xmax-hor.xori)/xinc) + new_nrow = math.floor((ymax-hor.yori)/yinc) + self.grid = Grid(hor.xori, hor.yori, new_ncol, new_nrow, xinc, yinc) self.nodes = self.grid.make_grid_arr() return diff --git a/warmth/mesh_model.py b/warmth/mesh_model.py index a04ceae..072d36d 100644 --- a/warmth/mesh_model.py +++ b/warmth/mesh_model.py @@ -455,7 +455,9 @@ def buildMesh(self,tti): """Construct a new mesh at the given time index tti, and determine the vertex re-indexing induced by dolfinx """ self.tti = tti + print("buildVertices") self.buildVertices(time_index=tti, useFakeEncodedZ=True) + print("constructMesh") self.constructMesh() print("updatemesh") self.updateMesh(tti) @@ -569,14 +571,14 @@ def constructMesh(self): print("saved mesh") enc = dolfinx.io.XDMFFile.Encoding.HDF5 with dolfinx.io.XDMFFile(MPI.COMM_SELF, fn, "r", encoding=enc) as file: - print("dol") + self.mesh = file.read_mesh(name="Grid" ) aa=file.read_meshtags(self.mesh, name="Grid") self.cell_data_layerID = np.floor(aa.values.copy()*1e-7)-3 self.node_index = np.mod(aa.values.copy(),1e7).astype(np.int32) # # obtain original vertex order as encoded in z-pos digits - print("done") + zz = self.mesh.geometry.x[:,2].copy() zz2 = np.mod(zz,1000) self.mesh_reindex = (1e-4+zz2*100).astype(np.int32) diff --git a/warmth/postprocessing.py b/warmth/postprocessing.py index 0dd0f5b..ae521c4 100644 --- a/warmth/postprocessing.py +++ b/warmth/postprocessing.py @@ -338,7 +338,7 @@ def _filter_sed_id_index(self,sed_id:int,sed_id_arr:np.ndarray)->Tuple[int,int]: class Results_interpolator: def __init__(self, builder,n_valid_node:int) -> None: self._builder = builder - self._values = ["kAsth","shf","qbase"] + self._values = ["kAsth","crustRHP","qbase","T0"] self._values_arr = ["subsidence","crust_ls","lith_ls"] self._n_age=None self.n_valid_node= n_valid_node+1 From 8b3915221c7ad5f44d25035ccd6d1fcbf8ef1322 Mon Sep 17 00:00:00 2001 From: Jussi Aittoniemi Date: Tue, 7 Nov 2023 13:05:29 +0100 Subject: [PATCH 17/21] post-merge fixes; support node interpolation to fixed_mesh_model.py and add it to the 3D simulation notebook; --- docs/notebooks/3D_simulation.ipynb | 61 +++++++++++++++++++++++++++++- subsheat3D/fixed_mesh_model.py | 42 ++++++++++++++++++++ warmth/mesh_model.py | 4 +- 3 files changed, 104 insertions(+), 3 deletions(-) diff --git a/docs/notebooks/3D_simulation.ipynb b/docs/notebooks/3D_simulation.ipynb index f0ab58f..9f93038 100644 --- a/docs/notebooks/3D_simulation.ipynb +++ b/docs/notebooks/3D_simulation.ipynb @@ -220,6 +220,55 @@ "model.simulator.run(save=True,purge=True)" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "# interpolate and extrapolate the missing nodes\n", + "# find nearby nodes from the array indexer_full_sim, which is sorted by x-index\n", + "import itertools\n", + "from subsheat3D.fixed_mesh_model import interpolateNode\n", + "for ni in range(len(model.builder.nodes)):\n", + " for nj in range(len(model.builder.nodes[ni])):\n", + " if model.builder.nodes[ni][nj] is False:\n", + " closest_x_up = []\n", + " for j in range(ni,len(model.builder.nodes[nj])):\n", + " matching_x = [ i[0] for i in model.builder.indexer_full_sim if i[0]==j ]\n", + " closest_x_up = closest_x_up + list(set(matching_x))\n", + " if len(matching_x)>0:\n", + " break\n", + " closest_x_down = []\n", + " for j in range(ni-1,-1,-1):\n", + " matching_x = [ i[0] for i in model.builder.indexer_full_sim if i[0]==j ]\n", + " closest_x_down = closest_x_down + list(set(matching_x))\n", + " if len(matching_x)>0:\n", + " break\n", + " closest_y_up = []\n", + " for j in range(nj,len(model.builder.nodes[ni])):\n", + " matching_y = [ i[1] for i in model.builder.indexer_full_sim if (i[1]==j and ((i[0] in closest_x_up) or i[0] in closest_x_down)) ]\n", + " closest_y_up = closest_y_up + list(set(matching_y))\n", + " if len(matching_y)>0:\n", + " break\n", + " closest_y_down = []\n", + " for j in range(nj-1,-1,-1):\n", + " matching_y = [ i[1] for i in model.builder.indexer_full_sim if (i[1]==j and (i[0] in closest_x_up or i[0] in closest_x_down) ) ]\n", + " closest_y_down = closest_y_down + list(set(matching_y))\n", + " if len(matching_y)>0:\n", + " break\n", + "\n", + " interpolationNodes = [ model.builder.nodes[i[0]][i[1]] for i in itertools.product(closest_x_up+closest_x_down, closest_y_up+closest_y_down) ]\n", + " interpolationNodes = [nn for nn in interpolationNodes if nn is not False]\n", + " node = interpolateNode(interpolationNodes)\n", + " node.X, node.Y = model.builder.grid.location_grid[ni,nj,:]\n", + " model.builder.nodes[ni][nj] = node\n", + " else:\n", + " node = interpolateNode([model.builder.nodes[ni][nj]]) # \"interpolate\" the node from itself to make sure the same member variables exist at the end\n", + " model.builder.nodes[ni][nj] = node\n" + ] + }, { "cell_type": "code", "execution_count": 15, @@ -246,7 +295,17 @@ ], "source": [ "from warmth.mesh_model import run\n", - "run(model,start_time=model.parameters.time_start,end_time=0)" + "import os\n", + "try:\n", + " os.mkdir('mesh')\n", + "except FileExistsError:\n", + " pass\n", + "try:\n", + " os.mkdir('temp')\n", + "except FileExistsError:\n", + " pass\n", + "run(model,start_time=model.parameters.time_start,end_time=0)\n", + "\n" ] }, { diff --git a/subsheat3D/fixed_mesh_model.py b/subsheat3D/fixed_mesh_model.py index 387f16b..eebdc84 100644 --- a/subsheat3D/fixed_mesh_model.py +++ b/subsheat3D/fixed_mesh_model.py @@ -1,7 +1,9 @@ import numpy as np from mpi4py import MPI from scipy.interpolate import LinearNDInterpolator +from typing import Iterator, List, Literal +from warmth.build import single_node from warmth.logging import logger from subsheat3D.Helpers import NodeParameters1D, top_crust,top_sed,thick_crust, top_lith, top_asth, top_sed_id, bottom_sed_id from subsheat3D.resqpy_helpers import write_tetra_grid_with_properties, write_hexa_grid_with_properties @@ -15,6 +17,46 @@ # NOTE: the default values of surface and base heat flow imply zero RHP in the crust # + +def interpolateNode(interpolationNodes: List[single_node], interpolationWeights=None) -> single_node: + assert len(interpolationNodes)>0 + if interpolationWeights is None: + interpolationWeights = np.ones([len(interpolationNodes),1]) + assert len(interpolationNodes)==len(interpolationWeights) + wsum = np.sum(np.array(interpolationWeights)) + iWeightNorm = [ w/wsum for w in interpolationWeights] + + node = single_node() + node.__dict__.update(interpolationNodes[0].__dict__) + node.X = np.sum( np.array( [node.X * w for node,w in zip(interpolationNodes,iWeightNorm)] ) ) + node.Y = np.sum( np.array( [node.Y * w for node,w in zip(interpolationNodes,iWeightNorm)] ) ) + + times = range(node.result._depth.shape[1]) + if node.subsidence is None: + node.subsidence = np.sum( np.array( [ [node.result.seabed(t) for t in times] * w for node,w in zip(interpolationNodes,iWeightNorm)] ) , axis = 0) + if node.crust_ls is None: + node.crust_ls = np.sum( np.array( [ [node.result.crust_thickness(t) for t in times] * w for node,w in zip(interpolationNodes,iWeightNorm)] ) , axis = 0) + if node.lith_ls is None: + node.lith_ls = np.sum( np.array( [ [node.result.lithosphere_thickness(t) for t in times] * w for node,w in zip(interpolationNodes,iWeightNorm)] ) , axis = 0) + + if node.beta is None: + node.beta = np.sum( np.array( [node.beta * w for node,w in zip(interpolationNodes,iWeightNorm)] ) , axis = 0) + if node.kAsth is None: + node.kAsth = np.sum( np.array( [node.kAsth * w for node,w in zip(interpolationNodes,iWeightNorm)] ) , axis = 0) + if node.kLith is None: + node.kLith = np.sum( np.array( [node.kLith * w for node,w in zip(interpolationNodes,iWeightNorm)] ) , axis = 0) + if node._depth_out is None: + node._depth_out = np.sum([node.result._depth_out*w for n,w in zip(interpolationNodes[0:1], [1] )], axis=0) + if node.temperature_out is None: + node.temperature_out = np.sum([n.result.temperature_out*w for n,w in zip(interpolationNodes[0:1], [1] )], axis=0) + + if node.sed is None: + node.sed = np.sum([n.sed*w for n,w in zip(interpolationNodes,iWeightNorm)], axis=0) + if node.sed_thickness_ls is None: + node.sed_thickness_ls = node.sed[-1,1,:] - node.sed[0,0,:] + return node + + # class UniformNodeGridFixedSizeMeshModel: """Manages a 3D heat equation computation using dolfinx diff --git a/warmth/mesh_model.py b/warmth/mesh_model.py index 072d36d..ef524b1 100644 --- a/warmth/mesh_model.py +++ b/warmth/mesh_model.py @@ -10,7 +10,7 @@ from .model import Model from warmth.logging import logger from .mesh_utils import NodeParameters1D, top_crust,top_sed,thick_crust, top_lith, top_asth, top_sed_id, bottom_sed_id,NodeGrid -from .resqpy_helpers import write_tetra_grid_with_properties, write_hexa_grid_with_properties,read_mesh_resqml +from .resqpy_helpers import write_tetra_grid_with_properties, write_hexa_grid_with_properties,read_mesh_resqml_hexa def tic(): #Homemade version of matlab tic and toc functions import time @@ -1374,4 +1374,4 @@ def run( model:Model, run_simulation=True, start_time=182, end_time=0, out_dir = print("total time solve: " , time_solve) EPCfilename = mm2.write_hexa_mesh_resqml("temp/") print("RESQML model written to: " , EPCfilename) - read_mesh_resqml(EPCfilename) # test reading of the .epc file \ No newline at end of file + read_mesh_resqml_hexa(EPCfilename) # test reading of the .epc file \ No newline at end of file From f6150c7682ad2d77e124c5f6eab1d6f069f437e5 Mon Sep 17 00:00:00 2001 From: Jussi Aittoniemi Date: Tue, 7 Nov 2023 14:26:56 +0100 Subject: [PATCH 18/21] Fix crust properties per-node --- subsheat3D/Helpers.py | 1 + warmth/mesh_model.py | 33 ++++++++++++++++----------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/subsheat3D/Helpers.py b/subsheat3D/Helpers.py index 7ce037b..2b76823 100644 --- a/subsheat3D/Helpers.py +++ b/subsheat3D/Helpers.py @@ -25,6 +25,7 @@ class NodeParameters1D: kCrust: float = 2.5 kAsth: float = 100 rhp: float = 2 + crustRHP: float = 2 crustliquid: float = 2500.0 crustsolid: float = 2800.0 lithliquid: float = 2700.0 diff --git a/warmth/mesh_model.py b/warmth/mesh_model.py index ef524b1..b0d76d4 100644 --- a/warmth/mesh_model.py +++ b/warmth/mesh_model.py @@ -9,7 +9,7 @@ from warmth.build import single_node from .model import Model from warmth.logging import logger -from .mesh_utils import NodeParameters1D, top_crust,top_sed,thick_crust, top_lith, top_asth, top_sed_id, bottom_sed_id,NodeGrid +from .mesh_utils import top_crust,top_sed,thick_crust, top_lith, top_asth, top_sed_id, bottom_sed_id,NodeGrid from .resqpy_helpers import write_tetra_grid_with_properties, write_hexa_grid_with_properties,read_mesh_resqml_hexa def tic(): #Homemade version of matlab tic and toc functions @@ -79,11 +79,8 @@ def __init__(self, model:Model, modelName="test", sedimentsOnly = False): self.mean_porosity = None self.c_rho = None self.numberOfSediments = model.builder.input_horizons.shape[0]-1 #skip basement - # self.globalSediments = self.node1D[0].sediments.copy() - self.parameters1D = self.node1D[0].parameters if hasattr(self.node1D[0], 'parameters') else NodeParameters1D() self.interpolators = {} - print("Using 1D Node parameters", self.parameters1D) def write_tetra_mesh_resqml( self, out_path): """Prepares arrays and calls the RESQML output helper function: the lith and aesth are removed, and the remaining @@ -315,17 +312,17 @@ def cRhoForLayerID(self, ss, node_index): # # prefactor 1000 is the heat capacity.. assumed constant # + node = self.node1D[node_index] if (ss==-1): - return 1000*self.parameters1D.crustsolid + return 1000*node.crustsolid if (ss==-2): - return 1000*self.parameters1D.lithsolid + return 1000*node.lithsolid if (ss==-3): - return 1000*self.parameters1D.crustsolid + return 1000*node.crustsolid if (ss>=0) and (ss len(self.node1D)-1): + print("cell ID", node_index, len(self.node1D)) + breakpoint() + node = self.node1D[node_index] if (ss==-1): - return self.parameters1D.kCrust + return node.kCrust elif (ss==-2): - return self.parameters1D.kLith + return node.kLith elif (ss==-3): - return self.parameters1D.kAsth + return node.kAsth elif (ss>=0) and (ss len(self.node1D)-1): - print("cell ID", node_index, len(self.node1D)) - breakpoint() - node = self.node1D[node_index] kc = node.sediments.k_cond[ss] return kc @@ -360,7 +357,9 @@ def rhpForLayerID(self, ss, node_index): """Radiogenic heat production for a layer ID index """ if (ss==-1): - return 0 + node = self.node1D[node_index] + kc = node.crustRHP + return kc elif (ss==-2): return 0 elif (ss==-3): From 066c7f287786d16b7481096f42aaa3fb7f9c0dcf Mon Sep 17 00:00:00 2001 From: Jussi Aittoniemi Date: Wed, 8 Nov 2023 17:55:33 +0100 Subject: [PATCH 19/21] WIP: test effect of crustal RHP in notebook. --- docs/notebooks/3D_simulation.ipynb | 6 +++++- warmth/mesh_model.py | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/notebooks/3D_simulation.ipynb b/docs/notebooks/3D_simulation.ipynb index 9f93038..5d71c12 100644 --- a/docs/notebooks/3D_simulation.ipynb +++ b/docs/notebooks/3D_simulation.ipynb @@ -266,7 +266,11 @@ " model.builder.nodes[ni][nj] = node\n", " else:\n", " node = interpolateNode([model.builder.nodes[ni][nj]]) # \"interpolate\" the node from itself to make sure the same member variables exist at the end\n", - " model.builder.nodes[ni][nj] = node\n" + " model.builder.nodes[ni][nj] = node\n", + " # if (model.builder.nodes[ni][nj].Y>12000):\n", + " # model.builder.nodes[ni][nj].crustRHP = (2e-6) * 4\n", + " # model.builder.nodes[ni][nj].rhp = (2e-6) * 4\n", + " model.builder.nodes[ni][nj].crustRHP = (2e0) * 1\n" ] }, { diff --git a/warmth/mesh_model.py b/warmth/mesh_model.py index b0d76d4..2772dd4 100644 --- a/warmth/mesh_model.py +++ b/warmth/mesh_model.py @@ -923,7 +923,7 @@ def setupSolverAndSolve(self, time_step=-1, no_steps=100, skip_setup = False, in # source = self.globalSediments.rhp[self.numberOfSediments-1] * 1e-6 # conversion from uW/m^3 # f = dolfinx.fem.Constant(self.mesh, PETSc.ScalarType(source)) # source term - f = self.rhpFcn * 1e-6 # conversion from uW/m^3 + f = self.rhpFcn # * 1e-6 # conversion from uW/m^3 print("mean RHP", np.mean(self.rhpFcn.x.array[:])) if ( self.useBaseFlux ): @@ -967,9 +967,9 @@ def marker(x): print("Neumann conditions: ", self.tti, np.count_nonzero(domain_c.x.array), np.count_nonzero(domain_zero.x.array)) g = (-1.0*baseFlux) * ufl.conditional( domain_c > 0, 1.0, 0.0 ) - L = (self.c_rho*self.u_n + dt*f)*v*ufl.dx - dt * g * v * ufl.ds # last term reflects Neumann BC + L = (self.c_rho*self.u_n + 1e-6*dt*f)*v*ufl.dx - dt * g * v * ufl.ds # last term reflects Neumann BC else: - L = (self.c_rho*self.u_n + dt*f)*v*ufl.dx # no Neumann BC + L = (self.c_rho*self.u_n + 1e-6*dt*f)*v*ufl.dx # no Neumann BC bilinear_form = dolfinx.fem.form(a) linear_form = dolfinx.fem.form(L) From c68989c0570c289e6f358d0308b6bfe9840013e0 Mon Sep 17 00:00:00 2001 From: Adam Cheng <52572642+adamchengtkc@users.noreply.github.com> Date: Fri, 10 Nov 2023 12:03:37 +0000 Subject: [PATCH 20/21] TST: pipeline to test 3D code --- .devcontainer/devcontainer.json | 15 ++++-- .github/workflows/python-test-3d.yml | 40 ++++++++++++++ .github/workflows/python-test.yml | 80 +++++++++++++++++----------- 3 files changed, 98 insertions(+), 37 deletions(-) create mode 100644 .github/workflows/python-test-3d.yml diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index f51ae42..f4f842a 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -16,14 +16,19 @@ // 👇 Configure tool-specific properties. "customizations": { "vscode": { - "extensions":["ms-python.python", "njpwerner.autodocstring","ms-python.autopep8"] + "extensions":[ + "ms-python.python", + "njpwerner.autodocstring", + "ms-python.autopep8", + "ms-toolsai.jupyter", + "ms-python.vscode-pylance", + "ms-azuretools.vscode-docker" + ] } }, "features": { - "ghcr.io/devcontainers/features/git-lfs:1": { - "autoPull": true, - "version": "latest" - } + "ghcr.io/devcontainers/features/git-lfs:1": {}, + "ghcr.io/devcontainers/features/docker-in-docker:2": {} } // 👇 Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. diff --git a/.github/workflows/python-test-3d.yml b/.github/workflows/python-test-3d.yml new file mode 100644 index 0000000..dc02aae --- /dev/null +++ b/.github/workflows/python-test-3d.yml @@ -0,0 +1,40 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Tests3D + +on: + workflow_call: + inputs: + event_type: + required: true + type: string + action_type: + required: true + type: string +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Run + uses: tj-actions/docker-run@v2 + with: + image: dolfinx/dolfinx:v0.7.0 + name: dolfinx + options: -v ${{ github.workspace }}:/warmth + args: | + cd /warmth + pip install . + pip install pytest==7.4.2 pytest-cov==4.1.0 + pytest --cov-report=term-missing --cov=warmth/3d tests/3d | tee pytest-coverage.txt + + + - name: Comment coverage + if: ${{ github.event_name == 'pull_request' && github.event.action == 'opened' }} + uses: coroo/pytest-coverage-commentator@v1.0.2 + with: + pytest-coverage: pytest-coverage.txt diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 0d50f8d..d139f02 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -2,47 +2,63 @@ # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions name: Tests - + on: workflow_dispatch: push: - branches: [ main, dev ] + branches: [main, dev] pull_request: - branches: [ main , dev ] + branches: [main, dev] jobs: build: - runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.10','3.11'] + python-version: ["3.10", "3.11"] steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - run: | - curl -sSL https://install.python-poetry.org | python3 - poetry install --with dev --no-interaction - - - name: Lint with flake8 - run: | - # stop the build if there are Python syntax errors or undefined names - poetry run flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude=docs - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - poetry run flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --exclude=docs - - - name: Test with pytest - run: | - poetry run pytest --cov-report=term-missing --cov=warmth tests/ | tee pytest-coverage.txt - - - name: Comment coverage - if: ${{ github.event_name == 'pull_request' && github.event.action == 'opened' }} - uses: coroo/pytest-coverage-commentator@v1.0.2 - with: - pytest-coverage: pytest-coverage.txt + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + curl -sSL https://install.python-poetry.org | python3 + poetry install --with dev --no-interaction + + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + poetry run flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude=docs + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + poetry run flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --exclude=docs + + - name: Test with pytest + run: | + poetry run pytest --cov-report=term-missing --ignore=tests/3d --cov=warmth tests/ | tee pytest-coverage.txt + + - name: Comment coverage + if: ${{ github.event_name == 'pull_request' && github.event.action == 'opened' }} + uses: coroo/pytest-coverage-commentator@v1.0.2 + with: + pytest-coverage: pytest-coverage.txt + + - name: check_3d_modified + uses: dorny/paths-filter@v2 + id: filter3d + with: + filters: | + mesh3d: + - 'warmth/3d/**' + outputs: + filter3d: ${{ steps.filter3d.outputs.mesh3d }} + + test3d: + uses: equinor/warmth/.github/workflows/python-test-3d.yml.yml@main + if: ${{ needs.build.outputs.filter3d }} == 'true' + with: + event_type: ${{ github.event_name}} + action_type: ${{ github.event.action}} From 820321379ebe3e422923164699b583c9eab3e937 Mon Sep 17 00:00:00 2001 From: Adam Cheng <52572642+adamchengtkc@users.noreply.github.com> Date: Fri, 10 Nov 2023 14:08:07 +0000 Subject: [PATCH 21/21] TST: CI pipelines --- .github/workflows/docs.yml | 1 + .github/workflows/publish.yml | 64 +++++++++++++++++++++++++++++++ .github/workflows/python-test.yml | 11 +++++- .github/workflows/snyk.yml | 23 +++++++++++ 4 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/publish.yml create mode 100644 .github/workflows/snyk.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index b51e845..aed70ee 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -5,6 +5,7 @@ name: docs on: workflow_dispatch: + workflow_call: push: branches: [ main ] diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..15666ba --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,64 @@ +# This workflows will upload a Python Package using Twine when a release is created +# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries + +name: build + +on: + workflow_dispatch: + +jobs: + test3d: + uses: equinor/warmth/.github/workflows/python-test-3d.yml@main + with: + event_type: ${{ github.event_name}} + action_type: ${{ github.event.action}} + test1d: + uses: equinor/warmth/.github/workflows/python-test.yml@main + with: + event_type: ${{ github.event_name}} + action_type: ${{ github.event.action}} + snyk: + uses: equinor/warmth/.github/workflows/snyk.yml@main + docs: + uses: equinor/warmth/.github/workflows/docs.yml@main + + deploy: + needs: [test3d, test1d, snyk, docs] + environment: deploy + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + with: + ref: 'master' + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.11' + + - name: Azure key vault login + uses: Azure/login@v1 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + - name: Fetch secrets + uses: azure/CLI@v1 + with: + azcliversion: 2.42.0 + inlineScript: | + echo PYPI_TOKEN=$(az keyvault secret show --vault-name PSSCloudDev --name PYPI-Token --query value -o tsv) >> $GITHUB_ENV + + - name: Install dependencies + run: | + curl -sSL https://install.python-poetry.org | python3 + poetry install --with dev --no-interaction + + - name: Build and publish + run: | + poetry config pypi-token.pypi ${{ env.PYPI_TOKEN }} + poetry build + poetry publish \ No newline at end of file diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index d139f02..5168f78 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -4,6 +4,14 @@ name: Tests on: + workflow_call: + inputs: + event_type: + required: true + type: string + action_type: + required: true + type: string workflow_dispatch: push: branches: [main, dev] @@ -57,7 +65,8 @@ jobs: filter3d: ${{ steps.filter3d.outputs.mesh3d }} test3d: - uses: equinor/warmth/.github/workflows/python-test-3d.yml.yml@main + needs: [build] + uses: equinor/warmth/.github/workflows/python-test-3d.yml@main if: ${{ needs.build.outputs.filter3d }} == 'true' with: event_type: ${{ github.event_name}} diff --git a/.github/workflows/snyk.yml b/.github/workflows/snyk.yml new file mode 100644 index 0000000..59918e5 --- /dev/null +++ b/.github/workflows/snyk.yml @@ -0,0 +1,23 @@ +name: Snyk scan +on: + push: + branches: [main, dev] + pull_request: + branches: [main, dev] + workflow_call: +jobs: + security: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Run Snyk to check for vulnerabilities + uses: snyk/actions/python-3.10@master + continue-on-error: false + env: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + with: + args: --sarif-file-output=snyk.sarif --severity-threshold=high + - name: Upload result to GitHub Code Scanning + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: snyk.sarif \ No newline at end of file