diff --git a/.gitignore b/.gitignore index 0f2a07fa3e7d44347af04ff682a5e37827e5bfe1..7d30c4e33027e919e91ba0767556ba8790578c80 100644 --- a/.gitignore +++ b/.gitignore @@ -3,9 +3,6 @@ __pycache__/ *.py[cod] -# pkglts files -.pkglts/info.log* - # Packages *.egg *.egg-info @@ -93,20 +90,15 @@ target/ # sphinx autogen file doc/_dvlpt/ - +doc/tutorials/ +doc/tutorials.rst # #} # user custom filters *.DS_Store report.txt -doc/*.ipynb *.h5 *.xdmf -doc/notebook.rst -doc/README.rst -doc/tutorials.rst *jitfailure-dolfin*/ -/doc/utils/ -/doc/_static/growth_scheme.png diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 67814915309c1ab25df76c07e703238e4962eee8..5676eda58b81c191abd244a546bb27b720c4465f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,7 +4,11 @@ stages: - build - test - deploy + - post-deploy + - release +variables: + TUTORIALS_ZIP: "tutorials.zip" conda_build: stage: build @@ -30,6 +34,9 @@ conda_build: allow_failure: true - if: '$CI_COMMIT_BRANCH == "master"' when: on_success + - if: $CI_COMMIT_BRANCH =~ /^hotfix/ + when: manual + allow_failure: true coverage: @@ -61,10 +68,18 @@ coverage: - coverage.xml when: always expire_in: 7 days - only: - - develop - - master - - /^release/ + rules: + - if: $CI_COMMIT_BRANCH =~ /^release/ + when: manual + allow_failure: true + - if: '$CI_COMMIT_BRANCH == "develop"' + when: manual + allow_failure: true + - if: '$CI_COMMIT_BRANCH == "master"' + when: always + - if: $CI_COMMIT_BRANCH =~ /^hotfix/ + when: manual + allow_failure: true anaconda : stage: deploy @@ -101,7 +116,7 @@ pages: image: continuumio/miniconda3 script: # Install required 'libgl1' & 'pyvista xvfb' system dependency: - - apt-get update && apt-get install libgl1 libgl1-mesa-glx xvfb wget -y + - apt-get update && apt-get install libgl1 libgl1-mesa-glx xvfb wget zip -y # Download and install pandoc3.1.12.3 (version must be at least (2.14.2) but less than (4.0.0)): - wget https://github.com/jgm/pandoc/releases/download/3.1.12.3/pandoc-3.1.12.3-1-amd64.deb - dpkg -i pandoc-3.1.12.3-1-amd64.deb @@ -115,6 +130,8 @@ pages: - python -m pip install -e .[doc] # Build the documentation with Sphinx: - sphinx-build -b html doc/ public/ + - echo "Zipping the tutorials folder..." + - zip -r public/tutorials.zip tutorials/ retry: max: 2 when: runner_system_failure @@ -123,7 +140,7 @@ pages: paths: - public exclude: - - public/*.ipynb + - public/**/*.ipynb dependencies: [] rules: - if: $CI_COMMIT_BRANCH =~ /^release/ @@ -134,6 +151,9 @@ pages: allow_failure: true - if: '$CI_COMMIT_BRANCH == "master"' when: on_success + - if: $CI_COMMIT_BRANCH =~ /^hotfix/ + when: manual + allow_failure: true docker_build_deploy: image: docker:latest @@ -154,4 +174,4 @@ docker_build_deploy: when: runner_system_failure dependencies: [] only: - - tags + - tags \ No newline at end of file diff --git a/AUTHORS.rst b/AUTHORS.rst index 55cf4cab5901be7f642a44f09d8c3163f7eeab6f..454cdc1d09e369a3d951aa3bb6aeb745ec4eb0a4 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -5,17 +5,21 @@ Credits Development Lead ---------------- -.. {# pkglts, doc.authors +* Manuel Petit, <manuel.petit@inria.fr> -* Florian Gacon, <florian.gacon@inria.fr> - -.. #} -Contributors +Main Contributors ------------ -.. {# pkglts, doc.contributors - * ALI Olivier <olivier.ali@inria.fr> +* Florian Gacon, <florian.gacon@inria.fr> + + +Other Contributors +------------------ + +* Jonathan Legrand +* Guillaume Cerutti +* Adrien Heymans +* Gonzalo Revilla -.. #} diff --git a/HISTORY.rst b/HISTORY.rst index 45e1ef0001ca24d4d830e9114bcdbea7ddd25a8d..8c2da189233ddbcc8de0db2b073879e387282708 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -7,6 +7,19 @@ creation (2020-08-26) * First release on PyPI. +version 1.1.0 (2024-03-25) +-------------------------- + +### **New Features** + +1. Added **PyVista** module `visu_pysta` for visualization. + +2. Improved handling of **3D geometry (tetra)** in `domains`. + +3. Improved access to solver parameters for better usability and flexibility: + + - List all the parameters that can be access in `parameter.py` + version 1.3.0 (2025-01-11) -------------------------- diff --git a/LICENSE b/LICENSE index 650a3f2c7442de4804864065ca70f3d0b8951a2e..57e7a5350ad168208e8b3d6e5c6b9864fb115a95 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,3 @@ -{# pkglts, license GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 @@ -153,6 +152,4 @@ Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent -authorization for you to choose that version for the Library. - -#} +authorization for you to choose that version for the Library. \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in index bfa8d262e4adabe18094820e1f5b511dc1eb9730..62ca18c91f5dab8cbafff464f7948ca025a39177 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,27 +1,19 @@ -# {# pkglts, pysetup +# Include important documentation files include AUTHORS.rst include CONTRIBUTING.rst include HISTORY.rst include README.rst - include LICENSE -include requirements.txt -include requirements_minimal.txt - - +# Include all test-related files for validating the library recursive-include test * - - -recursive-exclude * __pycache__ -recursive-exclude * *.py[co] - - +# Include all documentation files (e.g., Sphinx files and additional docs) recursive-include doc *.rst include doc/conf.py include doc/Makefile include doc/make.bat - -# #} +# Exclude unnecessary files and directories +recursive-exclude * __pycache__ +recursive-exclude * *.py[co] diff --git a/README.rst b/README.rst index 219c3e0868e98e91592897c94551a0064a6b25bd..cd32a0ddbf09ff911c6f33133eaa72ca54897da3 100644 --- a/README.rst +++ b/README.rst @@ -57,67 +57,60 @@ Requirements Installation ------------ -You will need conda in order to install ``bvpy``, you can download it `here <https://docs.conda.io/en/latest/miniconda.html>`_. +The full installation procedure is available `here <https://mosaic.gitlabpages.inria.fr/bvpy/>`_. -- From sources: +To install ``bvpy``, follow these steps: -.. code-block:: bash +1. **Using Anaconda** (recommended for most users): - git clone https://gitlab.inria.fr/mosaic/bvpy.git - cd bvpy - conda env create --file conda/env.yaml -n bvpy-dev - conda activate bvpy-dev - python -m pip install -e . + - Install with a new environment: -- From anaconda: + .. code-block:: bash -.. code-block:: bash + conda create -n bvpy -c mosaic -c conda-forge bvpy + conda activate bvpy - conda install -c mosaic bvpy + - Or install in an existing environment: + .. code-block:: bash -**Important Note:** Bvpy is currently running on *FEniCS legacy* and not *FEniCSx*. If you are using an ARM-based computer (mac M1s, M2s), there is currently no ARM-Friendly conda package of *FEniCS legacy*. But you can still run x86-based packages. to do so type the following: + conda activate <your-environment-name> + conda install -c mosaic -c conda-forge bvpy -.. code-block:: bash - - CONDA_SUBDIR=osx-64 conda create -n bvpy_x86 - conda activate bvpy_x86 - conda config --env --set subdir osx-64 +2. **Using Docker**: -Once this is done, you can import manually all the required libraries, listed within the `conda/env_osx.yaml` configuration file. + - Pull the Docker image: + .. code-block:: bash -- From docker + docker pull registry.gitlab.inria.fr/mosaic/bvpy:<TAG> -.. code-block:: bash + - Run the container and launch Jupyter Notebook: - docker pull registry.gitlab.inria.fr/mosaic/bvpy:<TAG> + .. code-block:: bash -Where <TAG> is the version of bvpy you want. You can find all the available version on the `repository <https://gitlab.inria.fr/mosaic/bvpy/container_registry/>`_. + docker run -it -p 8888:8888 registry.gitlab.inria.fr/mosaic/bvpy:<TAG> + jupyter notebook --ip 0.0.0.0 --no-browser --allow-root --port 8888 -To run the docker container and launch jupyter use: +3. **From Sources**: -.. code-block:: bash + - Clone the repository: - docker run -it -p 8888:8888 registry.gitlab.inria.fr/mosaic/bvpy:<TAG> - jupyter notebook --ip 0.0.0.0 --no-browser --allow-root --port 8888 + .. code-block:: bash -And use the URL on the terminal to open the tutorials. + git clone https://gitlab.inria.fr/mosaic/bvpy.git + cd bvpy + conda env create --file conda/env.yaml -n bvpy-dev + conda activate bvpy-dev + python -m pip install -e . -Test ----- - -If you install ``bvpy`` from sources install ``pytest`` and ``pytest-cov`` inside your environment: - -.. code-block:: bash - - python -m pip install -e ".[test]" - -And then run at the root of the project: - -.. code-block:: bash - - pytest -v --cov=bvpy test/ +.. The following content is commented out : no need + **Important Note:** Bvpy is currently running on *FEniCS legacy* and not *FEniCSx*. If you are using an ARM-based computer (mac M1s, M2s), there is currently no ARM-Friendly conda package of *FEniCS legacy*. But you can still run x86-based packages. to do so type the following: + .. code-block:: bash + CONDA_SUBDIR=osx-64 conda create -n bvpy_x86 + conda activate bvpy_x86 + conda config --env --set subdir osx-64 + Once this is done, you can import manually all the required libraries, listed within the `conda/env_osx.yaml` configuration file. Support ------- diff --git a/conda/env_20240207.yaml b/conda/env_20240207.yaml deleted file mode 100644 index 554b135747cd06666e6386da53bd0c805f16f288..0000000000000000000000000000000000000000 --- a/conda/env_20240207.yaml +++ /dev/null @@ -1,28 +0,0 @@ -name: bvpy -channels: - - conda-forge - - defaults -dependencies: - - python >=3.9,<4.0 - - ipython - - numpy - - pandas - - matplotlib - - python-gmsh=4.11 - - meshio - - fenics=2019.1 - - importlib_metadata - - notebook - - vtk - - plotly - - pyvista - - trame - - ipywidgets - - pytest - - pytest-cov - - sphinx - - sphinx_rtd_theme - - make - - nbsphinx - - recommonmark - - typing_extensions \ No newline at end of file diff --git a/conda/meta.yaml b/conda/meta.yaml index f9c5622a646f687a35748ea573a3a17d0d721f3b..709a8186ae35ca240e8df32376099ae909ce5c4a 100644 --- a/conda/meta.yaml +++ b/conda/meta.yaml @@ -1,6 +1,6 @@ {% set pyproject = load_file_data('../pyproject.toml', 'toml', from_recipe_dir=True) %} {% set project = pyproject.get('project', {}) %} -{% set deps = project.get('dependencies', {}) %} +{% set deps = project.get('dependencies', []) %} {% set urls = project.get('urls', {}) %} # https://docs.conda.io/projects/conda-build/en/latest/resources/define-metadata.html#loading-data-from-other-files # Source code for Jinja context: https://github.com/conda/conda-build/blob/main/conda_build/jinja_context.py @@ -25,6 +25,7 @@ requirements: - python {{ python }} - python-gmsh=4.11 - fenics=2019.1.0 + - h5py {% for dep in deps %} - {{ dep }} {% endfor %} diff --git a/data/tutorial_results/tuto_6_result_4_paraview_snapshot.png b/data/tutorial_results/tuto_6_result_4_paraview_snapshot.png deleted file mode 100644 index 1edb12898f9a16e45468f0cad925743c81187d7d..0000000000000000000000000000000000000000 Binary files a/data/tutorial_results/tuto_6_result_4_paraview_snapshot.png and /dev/null differ diff --git a/doc/_static/nonempty.txt b/doc/_static/nonempty.txt deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/doc/conf.py b/doc/conf.py index f4ae60995a112a302f8cae28a707bc84d13fce2f..eaa931499c0563a2707aa1ae4b580d6776e40ed1 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -1,4 +1,3 @@ -# {# pkglts, sphinx #!/usr/bin/env python # -*- coding: utf-8 -*- # @@ -17,98 +16,104 @@ import os import sys import shutil +from sphinx.application import Sphinx sys.path.insert(0, os.path.abspath('.')) # add doc/ to the path -from utils.convert_notebook import modify_notebook_for_sphinx - -def setup(app): - app.add_css_file('css/custom.css') - -# -------------------- Copy images -------------------- - -source_dir = '../tutorials/images' -target_dir = './_static' - -os.makedirs(target_dir, exist_ok=True) - -for filename in os.listdir(source_dir): - if filename.endswith('.png'): - source_file = os.path.join(source_dir, filename) - target_file = os.path.join(target_dir, filename) - - # Remove the target file if it exists - if os.path.exists(target_file): - os.remove(target_file) - - # Copy the source file to the target location - shutil.copyfile(source_file, target_file) - print(f'Copied {source_file} to {target_file}') - -# -------------------- Import Notebooks -------------------- - -for file in os.listdir('.'): - if '.ipynb' in file: - os.remove(file) - if file == 'tutorials.rst': - os.remove(file) - -notebooks = os.listdir('../tutorials') - -to_import = ['bvpy_tutorial_1_hello_world.ipynb', - 'bvpy_tutorial_2_domains.ipynb', - 'bvpy_tutorial_3_vforms.ipynb', - 'bvpy_tutorial_4_bnd_cond.ipynb', - 'bvpy_tutorial_5_reaction_diffusion.ipynb', - 'bvpy_tutorial_6_linear_elasticity.ipynb', - 'bvpy_tutorial_7_hyper_elasticity.ipynb', - 'bvpy_tutorial_8_pyvista.ipynb', - 'bvpy_tutorial_9_gmsh.ipynb', - 'bvpy_tutorial_10_morphoelasticity.ipynb'] - -# - Copy & update notebook if needed (pyvista is used ?) -for file in notebooks: - if file in to_import: - print(f'Importing file: {file}') - shutil.copy(f'../tutorials/{file}', file) - modify_notebook_for_sphinx(file, inplace=True, dynamic_plot=False) # dynamic_plot should be set to True when - # vtk.js have been updated - -with open("tutorials.rst", "w+") as f: - f.write(".. _tutorials:\n\n") - f.write("Tutorials\n") - f.write("*********\n\n") - f.write(".. toctree::\n") - f.write("\t:titlesonly:\n") - f.write("\t:maxdepth: 1\n\n") - for i in to_import: - f.write("\t"+i.replace(".ipynb", "")+"\n") - - -# If extensions (or modules to document with autodoc) are in another -# directory, add these directories to sys.path here. If the directory is -# relative to the documentation root, use os.path.abspath to make it -# absolute, like shown here. -# sys.path.insert(0, os.path.abspath('.')) - -# Get the project root dir, which is the parent dir of this -cwd = os.getcwd() -project_root = os.path.dirname(cwd) -src_dir = os.path.abspath(os.path.join(project_root, "src")) - -# Insert the project root dir as the first element in the PYTHONPATH. -# This lets us ensure that the source package is imported, and that its -# version is used. -sys.path.insert(0, os.path.join(project_root, 'src')) +#sys.path.insert(0, os.path.abspath('./tutorials')) # Add the `tutorials` directory + +from utils.convert_notebook import process_notebook, clean_and_reset_notebook + +# ---------------------- Process Tutorials ---------------------- # +source_tutorials_dir = '../tutorials' +tutorial_output_dir = './tutorials' +tutorial_rst_path = "tutorials.rst" + +# List of tutorial notebooks to process +notebooks = [ + 'bvpy_tutorial_1_hello_world.ipynb', + 'bvpy_tutorial_2_domains.ipynb', + 'bvpy_tutorial_3_vforms.ipynb', + 'bvpy_tutorial_4_bnd_cond.ipynb', + 'bvpy_tutorial_5_reaction_diffusion.ipynb', + 'bvpy_tutorial_6_linear_elasticity.ipynb', + 'bvpy_tutorial_7_hyper_elasticity.ipynb', + 'bvpy_tutorial_8_pyvista.ipynb', + 'bvpy_tutorial_9_gmsh.ipynb', + 'bvpy_tutorial_10_morphoelasticity.ipynb', +] -# import bvpy as mypkg +# Ensure the tutorials directory exists +os.makedirs(tutorial_output_dir, exist_ok=True) + +# Copy supporting folders (images, tutorial_data) +folders_to_copy = ['images', 'tutorial_data'] +for folder_name in folders_to_copy: + source_folder = os.path.join(source_tutorials_dir, folder_name) + target_folder = os.path.join(tutorial_output_dir, folder_name) + if os.path.exists(source_folder): + shutil.rmtree(target_folder, ignore_errors=True) # Clear old data + shutil.copytree(source_folder, target_folder) + print(f"Copied {source_folder} -> {target_folder}") + else: + print(f"Source folder {source_folder} not found. Skipping...") + +# Remove the old tutorials.rst file +if os.path.exists(tutorial_rst_path): + print(f"Removing old {tutorial_rst_path}") + os.remove(tutorial_rst_path) + +# Generate tutorials.rst with a toctree +tutorial_rst_lines = [ + ".. _tutorials:\n\n", + "Tutorials\n*********\n\n", + "Download the tutorial notebooks and accompanying data as a ZIP file: " + "`tutorials.zip <https://mosaic.gitlabpages.inria.fr/bvpy/tutorials.zip>`_\n\n\n", + ".. toctree::\n", + " :titlesonly:\n", + " :maxdepth: 1\n\n", +] -# -- General configuration --------------------------------------------- +# Process each notebook +for notebook in notebooks: + input_path = os.path.join(source_tutorials_dir, notebook) + output_path = os.path.join(tutorial_output_dir, notebook) + process_notebook(input_notebook_path=input_path, output_notebook_path=output_path, dynamic_plot=False) + tutorial_rst_lines.append(f" tutorials/{notebook.replace('.ipynb', '')}\n") + +# Write the tutorials.rst file +with open(tutorial_rst_path, "w", encoding="utf-8") as f: + f.writelines(tutorial_rst_lines) +print(f"Wrote {tutorial_rst_path}") + +# -------------------- Clean Tutorials After Build -------------------- # +def clean_doc(app: Sphinx, exception): + """ + Remove temporary folders created during the build process. + Cleans up the tutorials folder and the _dvlpt folder after the build is complete. + """ + # clean the tutorials/ folder in order to zip its content in CI/CD + if os.path.exists(tutorial_output_dir): + print(f"Cleaning up notebooks in {tutorial_output_dir}") + for notebook in notebooks: + notebook_path = os.path.join(tutorial_output_dir, notebook) + clean_and_reset_notebook(input_notebook_path=notebook_path, output_notebook_path=notebook_path) + + # Clean up the _dvlpt folder + dev_folder = os.path.abspath(os.path.join(project_root, "doc", "_dvlpt")) + if os.path.exists(dev_folder): + print(f"Cleaning up {dev_folder}") + shutil.rmtree(dev_folder, ignore_errors=True) + +# ---------------------- Sphinx Setup ---------------------- # +def setup(app): + """Hook custom setup functionality into Sphinx.""" + app.add_css_file('css/custom.css') # Add custom CSS + app.connect('build-finished', clean_doc) -# If your documentation needs a minimal Sphinx version, state it here. -# needs_sphinx = '1.0' +# ------------------- General Configuration ------------------- # +project_root = os.path.dirname(os.getcwd()) +sys.path.insert(0, os.path.join(project_root, 'src')) # Add src/ to the path -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', @@ -121,11 +126,12 @@ extensions = [ 'sphinx.ext.napoleon', 'sphinx.ext.todo', 'sphinx.ext.viewcode', + 'sphinx_tabs.tabs', 'nbsphinx', 'recommonmark', 'pyvista.ext.plot_directive', 'pyvista.ext.viewer_directive', - 'sphinx_design' + 'sphinx_design', ] # Enable plotly figures and add custom CSS for hiding cells with `remove_cell` tags @@ -134,263 +140,63 @@ nbsphinx_prolog = r""" <script src='http://cdnjs.cloudflare.com/ajax/libs/require.js/2.1.10/require.min.js'></script> <script>require=requirejs;</script> - """ -nbsphinx_timeout = -1 -# try to add more extensions which are not default -# but still useful -# based on the fact that the extension is installed on the system - -#try: -# import matplotlib.sphinxext.plot_directive -# extensions.append('matplotlib.sphinxext.plot_directive') -#except ImportError: -# pass - - - -# default settings that can be redefined outside of the pkglts block +nbsphinx_timeout = -1 todo_include_todos = True autosummary_generate = True -intersphinx_mapping = {'python': ('https://docs.python.org/3.4', None), - # 'dolfin': ('https://fenicsproject.org/docs/dolfin/latest/python/', None), - 'dolfin': ('https://fenicsproject.org/olddocs/dolfin/2019.1.0/python/', None), - 'ufl': ('https://fenics.readthedocs.io/projects/ufl/en/latest/', None)} -inheritance_node_attrs = dict(shape='ellipse', fontsize=12, - color='orange', style='filled') +intersphinx_mapping = { + 'python': ('https://docs.python.org/3.4', None), + 'dolfin': ('https://fenicsproject.org/olddocs/dolfin/2019.1.0/python/', None), + 'ufl': ('https://fenics.readthedocs.io/projects/ufl/en/latest/', None), +} +inheritance_node_attrs = dict(shape='ellipse', fontsize=12, color='orange', style='filled') -# Add any paths that contain templates here, relative to this directory. +# Templates, source files, and static paths templates_path = ['_templates'] - -# The suffix of source filenames. source_suffix = '.rst' - -# The encoding of source files. -# source_encoding = 'utf-8-sig' - -# The master toctree document. master_doc = 'index' +exclude_patterns = ['build', 'dist', '**.ipynb_checkpoints'] -# General information about the project. -project = u"bvpy" -copyright = u"2020, bvpy" - -# The version info for the project you're documenting, acts as replacement -# for |version| and |release|, also used in various other places throughout -# the built documents. -# - -# The short X.Y version. +# General information +project = "bvpy" +copyright = "2020, bvpy" version = "1.3.0" -# The full version, including alpha/beta/rc tags. release = "1.3.0" - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# language = None - -# There are two options for replacing |today|: either, you set today to -# some non-false value, then it is used: -# today = '' -# Else, today_fmt is used as the format for a strftime call. -# today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['build', 'dist', '**.ipynb_checkpoints'] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -# default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -# add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -# add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -# show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. +# Syntax highlighting style pygments_style = 'sphinx' -# A list of ignored prefixes for module index sorting. -# modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built -# documents. -# keep_warnings = False - - -# -- Options for HTML output ------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. +# ------------------- HTML Output ------------------- # html_theme = "sphinx_rtd_theme" - -# Theme options are theme-specific and customize the look and feel of a -# theme further. For a list of options available for each theme, see the -# documentation. -# html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -# html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# "<project> v<release> documentation". -# html_title = None - -# A shorter title for the navigation bar. Default is the same as -# html_title. -# html_short_title = None - -# The name of an image file (relative to this directory) to place at the -# top of the sidebar. html_logo = "_static/logo_side.png" - -# The name of an image file (within the static path) to use as favicon -# of the docs. This file should be a Windows icon file (.ico) being -# 16x16 or 32x32 pixels large. html_favicon = "_static/favicon.ico" - -# Add any paths that contain custom static files (such as style sheets) -# here, relative to this directory. They are copied after the builtin -# static files, so a file named "default.css" will overwrite the builtin -# "default.css". html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page -# bottom, using the given strftime format. -# html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -# html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -# html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names -# to template names. -# html_additional_pages = {} - -# If false, no module index is generated. -# html_domain_indices = True - -# If false, no index is generated. -# html_use_index = True - -# If true, the index is split into individual pages for each letter. -# html_split_index = False - -# If true, links to the reST sources are added to the pages. -# html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. -# Default is True. -# html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. -# Default is True. -# html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages -# will contain a <link> tag referring to it. The value of this option -# must be the base URL from which the finished HTML is served. -# html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -# html_file_suffix = None - -# Output file base name for HTML help builder. htmlhelp_basename = "bvpydoc" - -# -- Options for LaTeX output ------------------------------------------ - +# ------------------- LaTeX Output ------------------- # latex_elements = { 'preamble': r''' - \usepackage{amsmath} % For math expressions - \usepackage{amssymb} % For symbols like \nabla + \usepackage{amsmath} + \usepackage{amssymb} ''', } - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass -# [howto/manual]). -latex_documents = [ - (u"index", u"bvpy.tex", - u"bvpy Documentation", - u"moi", u"manual"), -] - -# The name of an image file (relative to this directory) to place at -# the top of the title page. -# latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings -# are parts, not chapters. -# latex_use_parts = False - -# If true, show page references after internal links. -# latex_show_pagerefs = False - -# If true, show URL addresses after external links. -# latex_show_urls = False - -# Documents to append as an appendix to all manuals. -# latex_appendices = [] - -# If false, no module index is generated. -# latex_domain_indices = True - - -# -- Options for manual page output ------------------------------------ - # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (u"index", u"bvpy", - u"bvpy Documentation", - [u"moi"], 1) +latex_documents = [ + ('index', 'bvpy', 'bvpy Documentation', ['moi'], 1), ] -# If true, show URL addresses after external links. -# man_show_urls = False - - -# -- Options for Texinfo output ---------------------------------------- - +# ------------------- Texinfo Output ------------------- # # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (u"index", u"bvpy", - u"bvpy Documentation", - u"moi", - u"bvpy", - u"A package implementing Boundary Value Problem.", - u"Miscellaneous"), + ('index', 'bvpy', 'bvpy Documentation', 'moi', 'bvpy', 'A package implementing Boundary Value Problem.', + 'Miscellaneous'), ] -# Documents to append as an appendix to all manuals. -# texinfo_appendices = [] - -# If false, no module index is generated. -# texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -# texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -# texinfo_no_detailmenu = False - -# use apidoc to generate developer doc +# ------------------- Apidoc generation ------------------- # try: from sphinx.ext.apidoc import main except ImportError: @@ -402,6 +208,5 @@ destdir = os.path.abspath(os.path.join(project_root, "doc", "_dvlpt")) if not os.path.isdir(destdir): os.makedirs(destdir) +src_dir = os.path.abspath(os.path.join(project_root, "src")) main(['-e', '-o', destdir, '-d', '4', '-s', source_suffix[1:], '--force', src_dir]) - -# #} diff --git a/doc/index.rst b/doc/index.rst index cf290da8c46c8cb15672ce0c0d0bd74041352635..cbc505cef6520efb976976dac8b6662554645cb8 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -31,4 +31,213 @@ Sources <_dvlpt/modules> -.. include:: ../README.rst +Welcome +------- + +.. image:: https://joss.theoj.org/papers/10.21105/joss.02831/status.svg + :target: https://doi.org/10.21105/joss.02831 + +**Bvpy** is a python library, based on `FEniCS <https://fenicsproject.org/>`_ and `GMSH <http://gmsh.info/>`_, to easily implement and study numerically Boundary Value Problems (BVPs) as well as Initial Boundary Value Problems (IBVPs) through the Finite Element Method (FEM). + +Initially built up in the context of developmental biology and morphomechanics, **Bvpy** proposes an intuitive API as close as possible to the formal definition of BVPs. Its purpose is to enable users to quickly and efficiently estimate the behavior of a wide variety of fields (scalars, vectors, tensors) on biologically relevant structures (Riemannian and non-Rieamannian manifolds), inspired by biophysical and biochemical processes (morphogene patterning, active matter mechanics, diffusion-reaction processes, active transports...). + +Despite this biological motivation, the **Bvpy** library has been implemented in an *agnostic* manner that makes it suitable for many other scientific context. + + +.. sidebar:: **Bvpy** + + :Lead Development: Manuel Petit + + :Coordination: Olivier Ali + + :Main Contributors: Florian Gacon + + :Other Contributors: Guillaume Cerutti, Adrien Heymans, Jonathan Legrand, Gonzalo Revilla + + :Active team: Inria project team `Mosaic <https://team.inria.fr/mosaic/>`_ + + :Institutes: `Inria <http://www.inria.fr>`_ + + :Language: Python + + :Supported OS: Linux, MacOS + + :Licence: `LGPL` + + :Funding: Inria ADT Gnomon (Christophe Godin) + + +Documentation +------------- + +A quick introduction to boundary-value problems and Initial-Boundary-value problems, as well as a the general philosophy behind the **Bvpy** library development can be found `here <https://mosaic.gitlabpages.inria.fr/bvpy/general_introduction.html>`_. + +A detailed description of the main classes and models of the library can be found `here <https://mosaic.gitlabpages.inria.fr/bvpy/library_description.html>`_. + +Some tutorials explaining basic manipulations are gathered `here <https://mosaic.gitlabpages.inria.fr/bvpy/tutorials.html>`_. + + +Requirements +------------ + +* Python 3.9 +* FEniCS 2019.1.0 +* GMSH 4.11 + + +Installation +------------ + +You will need conda in order to install ``bvpy``. If you don’t have conda installed, you can download it `here <https://docs.conda.io/en/latest/miniconda.html>`_. + +.. tabs:: + + .. tab:: Installing from Anaconda + + ``bvpy`` can be installed directly from the `Anaconda` distribution using the **conda package manager**. You have the flexibility to either create a new environment or install it into an existing one. + + 1. Open a terminal or Conda environment prompt. + + 2. Choose one of the following installation methods: + + - **Create a New Environment** (recommended): + + .. code-block:: bash + + conda create -n bvpy -c mosaic -c conda-forge bvpy + conda activate bvpy + + - **Install into an Existing Environment**: + + .. code-block:: bash + + conda activate <your-environment-name> + conda install -c mosaic -c conda-forge bvpy + + 3. Verify the ``bvpy`` installation: + + .. code-block:: bash + + python -c "from bvpy.utils.examples import cantilever_beam; cantilever_beam()" + + This should produce a Pyvista Plotter showing the displacement field of the cantilever beam. If you see the correctly rendered visualization, the installation and dependencies of ``bvpy`` are working as expected. + + **Note**: Replace `<your-environment-name>` with the name of your existing environment. + + .. tab:: Installing from Docker + + To install ``bvpy`` using Docker, follow these steps: + + 1. Pull the Docker image: + + .. code-block:: bash + + docker pull registry.gitlab.inria.fr/mosaic/bvpy:<TAG> + + Replace `<TAG>` with the desired version of ``bvpy``. + You can find all available versions on the `repository <https://gitlab.inria.fr/mosaic/bvpy/container_registry/>`_. + + 2. Run the Docker container and launch Jupyter Notebook: + + .. code-block:: bash + + docker run -it -p 8888:8888 registry.gitlab.inria.fr/mosaic/bvpy:<TAG> + jupyter notebook --ip 0.0.0.0 --no-browser --allow-root --port 8888 + + Use the URL displayed in the terminal to open the Jupyter Notebook in your browser for running tutorials. + + .. tab:: Installing from Sources + + If you want to install ``bvpy`` from its source code (e.g., for development purposes or using the latest version), use the following steps: + + 1. Clone the Repository: + + .. code-block:: bash + + git clone https://gitlab.inria.fr/mosaic/bvpy.git + cd bvpy + + 2. Create a Development Environment: + + Use the provided `conda` environment file to create a new development environment: + + .. code-block:: bash + + conda env create --file conda/env.yaml -n bvpy-dev + + After the environment is created, activate it: + + .. code-block:: bash + + conda activate bvpy-dev + + 3. Install ``bvpy`` in Editable Mode: + + Install the repository in editable mode (using pip) to allow changes to the source code to take effect immediately: + + .. code-block:: bash + + python -m pip install -e . + + 4. Verify the ``bvpy`` installation: + + .. code-block:: bash + + python -c "from bvpy.utils.examples import cantilever_beam; cantilever_beam()" + + This should produce a Pyvista Plotter showing the displacement field of the cantilever beam. If you see + the correctly rendered visualization, the installation and dependencies of ``bvpy`` are working as expected. + + **Optional: Run Tests** + + If you installed ``bvpy`` from sources, install ``pytest`` and ``pytest-cov`` inside your environment: + + .. code-block:: bash + + python -m pip install -e ".[test]" + + Then, run the tests from the root of the project directory to ensure that everything works correctly: + + .. code-block:: bash + + pytest -v --cov=bvpy test/ + + +.. The following content is commented out : no need + **Important Note:** Bvpy is currently running on *FEniCS legacy* and not *FEniCSx*. If you are using an ARM-based computer (mac M1s, M2s), there is currently no ARM-Friendly conda package of *FEniCS legacy*. But you can still run x86-based packages. to do so type the following: + .. code-block:: bash + CONDA_SUBDIR=osx-64 conda create -n bvpy_x86 + conda activate bvpy_x86 + conda config --env --set subdir osx-64 + Once this is done, you can import manually all the required libraries, listed within the `conda/env_osx.yaml` configuration file. + +Support +------- + +If you encounter an error or need help, please `raise an issue <https://gitlab.com/oali/bvpy/-/issues>`_. + +Contributing +------------ + +Bvpy is a is a collaborative project and contributions are welcome. If you want to contribute, please contact the `coordinator <mailto:olivier.ali@inria.fr>`_ prior to any `merge request <https://gitlab.com/oali/bvpy/-/merge_requests>`_ and +check the `gitlab merge request guidelines <https://docs.gitlab.com/ee/development/contributing/merge_request_workflow.html>`_ if needed. + +Citation +-------- + +If you are using Bvpy in a published work, please use this bibtex entry as reference: + +.. code-block:: + + @article{Gacon2021, + doi = {10.21105/joss.02831}, + url = {https://doi.org/10.21105/joss.02831}, + year = {2021}, + publisher = {The Open Journal}, + volume = {6}, + number = {59}, + pages = {2831}, + author = {Florian Gacon and Christophe Godin and Olivier Ali}, + title = {BVPy: A FEniCS-based Python package to ease the expression and study of boundary value problems in Biology.}, + journal = {Journal of Open Source Software}} + diff --git a/doc/utils/convert_notebook.py b/doc/utils/convert_notebook.py index b9680bc70c6fd609e19805cf438071636edd01fa..bf256f226f5b216a005488aa16b7ed8f21092280 100644 --- a/doc/utils/convert_notebook.py +++ b/doc/utils/convert_notebook.py @@ -1,8 +1,142 @@ import nbformat import re +import os +from nbconvert import HTMLExporter +from nbconvert.preprocessors import ExecutePreprocessor, ClearOutputPreprocessor -def modify_notebook_for_sphinx(input_path, output_path=None, inplace=True, dynamic_plot=False): - """ Change the backend if pyvista is imported and update image paths. +def process_notebook(input_notebook_path, output_notebook_path, dynamic_plot=False): + """ Process a Jupyter notebook: Clear outputs, modify for PyVista and write to another path. + + This function orchestrates the following: + 1. Loads the notebook from the specified path. + 2. Clears all outputs. + 3. Modifies the notebook to support PyVista and Sphinx documentation. + 4. Write the notebook to the specified path + + Parameters + ---------- + input_notebook_path : str + Path to the input Jupyter notebook file (.ipynb). + output_notebook_path : str + Path to write the Jupyter notebook file (.ipynb). + dynamic_plot : bool, optional + Whether to enable PyVista dynamic plot mode, by default False. + + Raises + ------ + FileNotFoundError + If the notebook specified by `input_notebook_path` does not exist. + Exception + If any issues arise during the notebook clearing or modification + + Notes + ----- + + - This function is part of a workflow for preparing Jupyter notebooks for HTML-based documentation. + - By enabling `dynamic_plot`, PyVista's interactive plot mode is incorporated into the notebook. + """ + try: + # Step 1: Load the notebook from the specified path + if not os.path.exists(input_notebook_path): + raise FileNotFoundError(f"Notebook not found: {input_notebook_path}") + + with open(input_notebook_path, 'r', encoding='utf-8') as f: + notebook_content = nbformat.read(f, as_version=4) + print(f"Loaded notebook: {input_notebook_path}") + + # Step 2: Clear outputs + cleared_notebook = clear_notebook(notebook_content) + + # Step 3: Modify notebook content for PyVista + modified_notebook = modify_notebook_for_pyvista(cleared_notebook, + dynamic_plot=dynamic_plot) + + # Step 4: Write the modified notebook to the specified output path + with open(output_notebook_path, 'w', encoding='utf-8') as f: + nbformat.write(modified_notebook, f) + print(f"Modified notebook written to: {output_notebook_path}") + + except Exception as e: + print(f"Unexpected error during notebook processing: {e}") + return None + +def clean_and_reset_notebook(input_notebook_path, output_notebook_path): + """ + Clean and reset a Jupyter notebook. + + Steps: + 1. Open the input notebook. + 2. Remove output cells using clear_notebook. + 3. Undo modifications made by modify_notebook_for_pyvista by removing specific cells. + 4. Save the cleaned and reset notebook to the specified output path. + + Parameters + ---------- + input_notebook_path : str + Path to the input Jupyter notebook file (.ipynb). + output_notebook_path : str + Path to save the cleaned and reset Jupyter notebook file (.ipynb). + + """ + try: + # Step 1: Load the notebook + if not os.path.exists(input_notebook_path): + raise FileNotFoundError(f"Notebook not found: {input_notebook_path}") + + with open(input_notebook_path, 'r', encoding='utf-8') as f: + notebook_content = nbformat.read(f, as_version=4) + print(f"Loaded notebook: {input_notebook_path}") + + # Step 2: Clear all notebook outputs + cleared_notebook = clear_notebook(notebook_content) + + # Step 3: Undo modifications from `modify_notebook_for_pyvista` + cleaned_cells = [] + for cell in cleared_notebook["cells"]: + # Only keep the cell if it doesn't match the pattern added by `modify_notebook_for_pyvista` + if cell["cell_type"] == "code" and re.search(r"# ---- for online doc only ---- #", cell["source"]): + print("Removing cell from notebook with PyVista modifications.") + continue # Skip this cell + # Add the cell to the cleaned list + cleaned_cells.append(cell) + + # Update the notebook content with the cleaned cells + cleared_notebook["cells"] = cleaned_cells + + # Step 4: Write the cleaned and reset notebook to output path + with open(output_notebook_path, 'w', encoding='utf-8') as f: + nbformat.write(cleared_notebook, f) + print(f"Cleaned and reset notebook written to: {output_notebook_path}") + + except Exception as e: + print(f"Error during notebook cleaning and resetting: {e}") + + +def clear_notebook(notebook_content): + """ Clear all outputs in a Jupyter notebook content. + + This function removes all cell outputs and return the notebook content. + + Parameters + ---------- + notebook_content : dict, optional + The loaded notebook content (as a Python dictionary). + Returns + ------- + dict + Cleared notebook content. + """ + try: + # Clear outputs from the notebook + clear_preprocessor = ClearOutputPreprocessor() + cleared_notebook, _ = clear_preprocessor.preprocess(notebook_content, {}) + print("Cleared notebook outputs.") + return cleared_notebook + except Exception as e: + print(f"Error while clearing notebook outputs: {e}") + +def modify_notebook_for_pyvista(notebook_content, dynamic_plot=False): + """ Modify notebook content for Sphinx documentation by changing pyvista backend configurations TODO: follow issues to know when a full dynamic plot with `html` can be done https://github.com/pyvista/pyvista/issues/6250 --> add_text (title) @@ -10,65 +144,47 @@ def modify_notebook_for_sphinx(input_path, output_path=None, inplace=True, dynam Parameters ---------- - input_path : str - input path of the notebook - output_path : str, optional - output path of the notebook, by default None - inplace : bool, optional - update the notebook inplace, by default True + notebook_content : dict + The loaded notebook content (as a Python dictionary). dynamic_plot : bool, optional - make the pyvista plot dynamic or static, by default False. + Make the pyvista plot dynamic or static, by default False. + + Returns + ------- + dict + Modified notebook content. """ - # Load the notebook - with open(input_path) as f: - nb = nbformat.read(f, as_version=4) - - # List to store modified cells - new_cells = [] - - # Should be set to `html` as soon as vtk.js supports text & categorical labelling - if dynamic_plot: - pyvista_flag = 'html' - else: - pyvista_flag = 'static' - - for cell in nb.cells: - # For markdown cells, update image references - if cell.cell_type == "markdown": - cell.source = re.sub( - r'(<img\s+src=")images/([^"]+)(".*?>)', - r'\1_static/\2\3', - cell.source - ) - - # Append the original cell - new_cells.append(cell) - - # Check if the cell is a code cell and contains 'import pyvista as pv' - if cell.cell_type == "code": - if 'import pyvista as pv' in cell.source: + try: + # List to store modified cells + new_cells = [] + + # Set the PyVista backend flag + pyvista_flag = 'html' if dynamic_plot else 'static' + + for cell in notebook_content['cells']: + # Append the original (or modified) cell + new_cells.append(cell) + + # Check if the cell is a code cell and contains 'import pyvista as pv' + if cell['cell_type'] == "code" and 'import pyvista as pv' in cell['source']: # Create a new cell with PyVista backend configuration new_cell = nbformat.v4.new_code_cell( source=( "# ---- for online doc only ---- #\n" - f"pv.set_jupyter_backend('{pyvista_flag}')\n" - "pv.start_xvfb()\n" - "import time\n" - "time.sleep(3) # Give time to xvfb to start \n" + f"pv.set_jupyter_backend('{pyvista_flag}')\n" + "pv.start_xvfb(3)\n" "# ---- end of online doc only ---- #" ) ) # Add metadata to hide the new cell in Sphinx-generated documentation - new_cell.metadata["nbsphinx"] = "hidden" + new_cell['metadata']["nbsphinx"] = "hidden" new_cells.append(new_cell) - # Update notebook cells with modified cells - nb.cells = new_cells - - # Save the modified notebook - if inplace or (output_path is None): - output_path = input_path + # Update notebook content + notebook_content['cells'] = new_cells + return notebook_content - with open(output_path, 'w') as f: - nbformat.write(nb, f) \ No newline at end of file + except Exception as e: + print(f"Error while modifying notebook: {e}") + return None \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 394a172df01cb6e6cd40585cf1ae1c7f35a34237..1cbeddae54624fdb47d6e2e851988ea2fda51e83 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,13 +16,34 @@ dependencies = [ "meshio", "importlib_metadata", "plotly", - "pyvista[all]", + "pyvista", "typing_extensions", "nbformat>=4.2.0", - "scipy" + "scipy", + "trame", + "trame-vtk", + "trame-vuetify", + "ipywidgets" ] license = {file = "LICENSE"} +[roles] +lead_development = [ + {name = "Manuel Petit", email = "manuel.petit@inria.fr"} +] +coordination = [ + {name = "Olivier Ali", email = "olivier.ali@inria.fr"} +] +main_contributors = [ + {name = "Florian Gacon", email = "florian.gacon@inria.fr"} +] +other_contributors = [ + {name = "Guillaume Cerutti"}, + {name = "Adrien Heymans"}, + {name = "Gonzalo Revilla"}, + {name = "Jonathan Legrand"} +] + [project.urls] Homepage = "https://mosaic.gitlabpages.inria.fr/bvpy/" Documentation = "https://mosaic.gitlabpages.inria.fr/bvpy/" diff --git a/src/bvpy/utils/examples.py b/src/bvpy/utils/examples.py new file mode 100644 index 0000000000000000000000000000000000000000..1bb1b3a9a5cc48eb8f5dbafdfefe00b1bd5a00d3 --- /dev/null +++ b/src/bvpy/utils/examples.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# bvpy.utils.examples +# +# File author(s): +# Manuel Petit <manuel.petit@inria.fr> +# +# File contributor(s): +# Manuel Petit <manuel.petit@inria.fr> +# +# File maintainer(s): +# Manuel Petit <manuel.petit@inria.fr> +# +# Copyright © by Inria +# Distributed under the LGPL License.. +# See accompanying file LICENSE.txt or copy at +# https://www.gnu.org/licenses/lgpl-3.0.en.html +# +# ----------------------------------------------------------------------- + +from bvpy.domains import Cylinder +from bvpy.boundary_conditions import dirichlet +from bvpy.vforms.elasticity import LinearElasticForm +from bvpy.utils.visu import plot +from bvpy.utils.visu_pyvista import visualize +from bvpy.bvp import BVP + +def cantilever_beam(visualizer: str='pyvista'): + """ Example of a cantilever beam simulation. + + This function models a cantilever beam as a cylindrical domain with clamped boundary conditions at one end and + a gravitational load applied along the negative Z-axis. The problem is solved using a variational formulation + for linear elasticity, and the solution is returned as a visualization of the displacement field. + + Parameters + ---------- + visualizer : str, optional + The type of visualizer to use for visualization purposes. Acceptable values are 'pyvista' (default) or + 'plotly'. + + Returns + ------- + None + This function visualizes the displacement field of the solved boundary value problem + either through a Pyvista Plotter or as a Plotly plot depending on the given `visualizer` + argument. + + Notes + ----- + The simulation steps include: + 1. Define the domain of the cantilever beam as a cylinder: + - Length of the cylinder: dx = 2 (unit length). + - Radius: r = 0.2. + - Meshing: tetrahedral cells with a size of 0.1. + - Algorithm: Frontal-Delaunay meshing. + - The domain is then discretized. + + 2. Define boundary conditions: + - Dirichlet boundary conditions are applied to the end of the cylinder at x=0: + All translations (displacements) in X, Y, and Z are fixed. + + 3. Define the variational formulation: + - Linear Elasticity Formulation. + - Material Parameters: + - Young's modulus = 1. + - Poisson's ratio = 0.3. + - Body force: + - Gravitational acceleration: g = 0.016. + - Density: Ï = 0.1. + - Source term: [0, 0, -Ï * g]. + + 4. Create and solve the boundary-value problem: + - Combine domain, boundary conditions, and variational form into a BVP instance. + - Solve the BVP for the cantilever beam. + + 5. Visualize the displacement field at the mechanical equilibrium state + + Examples + -------- + >>> from bvpy.utils.examples import cantilever_beam + >>> cantilever_beam() + """ + assert visualizer in ['pyvista', 'plotly'], "Acceptable values are 'pyvista' (default) or 'plotly'." + + # - Create a cylinder and set up some parameters for the mesh construction + cylinder = Cylinder(x=0, dx=2, dz=0, r=0.2, cell_type='tetra', + cell_size=.1, algorithm='Frontal-Delaunay', clear=True) + cylinder.discretize() # discretize (from Gmsh -> FeniCS) + + # - Create boundary conditions : clamp the beam at x=0 + bc = [dirichlet(val=[0, 0, 0], boundary="near(x, 0, 1e-2)")] + + # - Create the variational form + g = 0.016 + rho = .1 + vform = LinearElasticForm(young=1, + poisson=0.3, + source=[0, 0, -rho * g]) + + # - Create the boundary-value problem and solve it + bvp = BVP(domain=cylinder, vform=vform, bc=bc) + bvp.solve() + + # - Visualize the resulting displacement field + if visualizer == 'pyvista': + visualize(bvp, 'solution') + elif visualizer == 'plotly': + visualize(bvp.solution) diff --git a/src/bvpy/utils/io.py b/src/bvpy/utils/io.py index 6a30e947cb37c28521c6996f676521387edc3a75..054adedca64c7fbdc1bd704f06377acd57d37b7c 100644 --- a/src/bvpy/utils/io.py +++ b/src/bvpy/utils/io.py @@ -166,21 +166,26 @@ def read_polygonal_data(path): Values of these properties of each element of the considered topolgocial degree. + Raises + ------ + ValueError + If the file format is not supported. + """ - try: - assert path[-4:] in ['.txt', '.ply', '.obj'] - if path[-4:] == '.ply': - return read_polygonal_ply(path) + if path[-4:] not in ['.txt', '.ply', '.obj']: + raise ValueError( + f"The format of the given file {path} is not supported. " + "Supported formats are: '.txt', '.ply', '.obj'." + ) - elif path[-4:] == '.obj': - return read_polygonal_obj(path) + if path[-4:] == '.ply': + return read_polygonal_ply(path) - elif path[-4:] == '.txt': - return read_polygonal_txt(path) + elif path[-4:] == '.obj': + return read_polygonal_obj(path) - except AssertionError: - print('WARNING - utils.read():' - + 'the format of the given file is not supported.') + elif path[-4:] == '.txt': + return read_polygonal_txt(path) def read_polygonal_ply(path): @@ -218,7 +223,7 @@ def read_polygonal_ply(path): data = {0: None, 1: None, 2: None, 3: None} property_infos = {0: [], 1: [], 2: [], 3: []} - # -- Reading the data + # -- Reading the tutorial_data with open(path, 'r') as file: content = file.readlines() header = content[:content.index('end_header\n')] diff --git a/data/mesh.msh b/test/data/mesh_example.msh similarity index 100% rename from data/mesh.msh rename to test/data/mesh_example.msh diff --git a/data/simple_obj_example.obj b/test/data/obj_example.obj similarity index 100% rename from data/simple_obj_example.obj rename to test/data/obj_example.obj diff --git a/data/test_small.ply b/test/data/ply_example.ply similarity index 100% rename from data/test_small.ply rename to test/data/ply_example.ply diff --git a/data/tissue_example.txt b/test/data/tissue_example.txt similarity index 100% rename from data/tissue_example.txt rename to test/data/tissue_example.txt diff --git a/data/tissue_example_2.txt b/test/data/tissue_example_2.txt similarity index 100% rename from data/tissue_example_2.txt rename to test/data/tissue_example_2.txt diff --git a/test/test_io.py b/test/test_io.py index 354f22461a020226cddde59386b49a686d5ceaea..d293ceb46f8ff8f9b9f858a3e728806b3beff636 100644 --- a/test/test_io.py +++ b/test/test_io.py @@ -1,5 +1,6 @@ import unittest import tempfile +from pathlib import Path from fenics import UnitSquareMesh from bvpy import BVP @@ -19,45 +20,56 @@ class TestIO(unittest.TestCase): """ self.tmp = tempfile.TemporaryDirectory() + # Base directory where the `tutorial_data` folder resides + self.test_data_dir = Path(__file__).parent / "data" + def tearDown(self): """Concludes and closes the test. """ self.tmp.cleanup() def test_read_gmsh_data(self): - path = "data/mesh.msh" + path = str(self.test_data_dir / "mesh_example.msh") dom = CustomDomainGmsh(path) def test_read_polygonal_data_ply(self): - path = "data/test_small.ply" + path = str(self.test_data_dir / "ply_example.ply") points, cells, _ = read_polygonal_data(path) self.assertEqual(len(points), 10) self.assertEqual(len(cells), 1) def test_read_polygonal_data_obj(self): - path = "data/simple_obj_example.obj" + path = str(self.test_data_dir / "obj_example.obj") points, cells, _ = read_polygonal_data(path) self.assertEqual(len(points), 6) self.assertEqual(len(cells), 2) def test_read_polygonal_data_txt(self): - path = "data/tissue_example.txt" + path = str(self.test_data_dir / "tissue_example.txt") points, cells, _ = read_polygonal_data(path) self.assertEqual(len(points), 220) self.assertEqual(len(cells), 61) def test_read_polygonal_data_txt_2(self): - path = "data/tissue_example_2.txt" + path = str(self.test_data_dir / "tissue_example_2.txt") points, cells, label = read_polygonal_data(path) self.assertEqual(label[0], 0) self.assertEqual(label[-1], 60) self.assertEqual(len(label), 61) def test_read_polygonal_data_wrong_type(self): - path = "data/test_recording_mesh.xdmf" - read_polygonal_data(path) + path = str(self.test_data_dir / "test_recording_mesh.xdmf") + + with self.assertRaises(ValueError) as context: + read_polygonal_data(path) + + # Check that the exception message is correct + self.assertIn( + f"The format of the given file {path} is not supported.", + str(context.exception) + ) def test_write_ply_mesh(self): mesh = UnitSquareMesh(2, 2) diff --git a/tutorials/bvpy_tutorial_10_morphoelasticity.ipynb b/tutorials/bvpy_tutorial_10_morphoelasticity.ipynb index 2c3e00123e440386a9402afd573326c0e3f79480..e14c508fbdb2c1edb1bff26297c73eacc087a65a 100644 --- a/tutorials/bvpy_tutorial_10_morphoelasticity.ipynb +++ b/tutorials/bvpy_tutorial_10_morphoelasticity.ipynb @@ -10,6 +10,8 @@ "\n", "To help understand the concepts and tools used in this notebook, we provide a brief theoretical background and context for the methods implemented here.\n", "\n", + "Download the tutorial notebooks and accompanying data as a ZIP file: [tutorials.zip](https://mosaic.gitlabpages.inria.fr/bvpy/tutorials.zip).\n", + "\n", "## Overview of Functions and Methods\n", "\n", "This tutorial focuses on implementing and visualizing morphoelastic simulations using specialized classes and methods from `bvpy`. Here is an introduction to the main components utilized in this tutorial:\n", diff --git a/tutorials/bvpy_tutorial_1_hello_world.ipynb b/tutorials/bvpy_tutorial_1_hello_world.ipynb index 819f26bb05742b290d9d40e233a2a8d971e7c509..292058a77876c856b5700edd02b5c7eccfb75c89 100644 --- a/tutorials/bvpy_tutorial_1_hello_world.ipynb +++ b/tutorials/bvpy_tutorial_1_hello_world.ipynb @@ -14,9 +14,9 @@ "\n", "- Basic classes: `Rectangle()` (domain), `PoissonForm()` (vform), `BVP()` and main methods/functions: `.solve()`, `.info()`, `save()`, `plot()`...\n", "\n", - "- Error analys.\n", + "- Error analysis.\n", "\n", - "Download the notebook: [bvpy_tutorial_1](https://gitlab.inria.fr/mosaic/publications/bvpy/-/raw/develop/tutorials/bvpy_tutorial_1_hello_world.ipynb?inline=false)." + "Download the tutorial notebooks and accompanying data as a ZIP file: [tutorials.zip](https://mosaic.gitlabpages.inria.fr/bvpy/tutorials.zip)." ] }, { @@ -183,9 +183,11 @@ ] }, { - "metadata": {}, "cell_type": "markdown", - "source": "In its current state, **Bvpy** provides minimalist visualization and analysis tools. One may want to turn to other frameworks, such as **Paraview** for instance, to visualize and analyze simulation results. To do so, **Bvpy** enable the recording of simulation in the `.xdmf` format." + "metadata": {}, + "source": [ + "In its current state, **Bvpy** provides minimalist visualization and analysis tools. One may want to turn to other frameworks, such as **Paraview** for instance, to visualize and analyze simulation results. To do so, **Bvpy** enable the recording of simulation in the `.xdmf` format." + ] }, { "cell_type": "code", @@ -194,7 +196,7 @@ "outputs": [], "source": [ "from bvpy.utils.io import save\n", - "save(prblm_1, '../data/tutorial_results/tuto_1_result_1.xdmf')" + "save(prblm_1, 'tutorial_data/tuto_1_result_1.xdmf')" ] }, { @@ -411,9 +413,7 @@ { "cell_type": "markdown", "metadata": {}, - "source": [ - "> **Note:** The `cdata` and `bdata` attributes are `MeshFunctionSizet`, *i.e.* **FEniCS** functions defined on a given mesh returning integer values. They can be useful for parametrization, as we will see in tutorial #6." - ] + "source": "> **Note:** The `cdata` and `bdata` attributes are `MeshFunctionSizet`, *i.e.* **FEniCS** functions defined on a given mesh returning integer values. They can be useful for parametrization, as we will see in tutorial [Linear Elasticity](bvpy_tutorial_6_linear_elasticity.html)." }, { "cell_type": "markdown", @@ -480,7 +480,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -494,7 +494,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.9" + "version": "3.9.20" }, "latex_envs": { "LaTeX_envs_menu_present": true, diff --git a/tutorials/bvpy_tutorial_2_domains.ipynb b/tutorials/bvpy_tutorial_2_domains.ipynb index 526f4776bfb4e46ee6882ca0f75b99e56bab2b95..3f070177fef36c5ef6776d2fceed01f8b0b24e0a 100644 --- a/tutorials/bvpy_tutorial_2_domains.ipynb +++ b/tutorials/bvpy_tutorial_2_domains.ipynb @@ -23,7 +23,7 @@ "- Generation of domains from existing files: `CustomDomain` and `CustomPolygonalDomain` classes.\n", "\n", "\n", - "Download the notebook: [bvpy_tutorial_2](https://gitlab.inria.fr/mosaic/publications/bvpy/-/raw/develop/tutorials/bvpy_tutorial_2_domains.ipynb?inline=false)." + "Download the tutorial notebooks and accompanying data as a ZIP file: [tutorials.zip](https://mosaic.gitlabpages.inria.fr/bvpy/tutorials.zip)." ] }, { @@ -392,7 +392,7 @@ "source": [ "from bvpy.domains import CustomDomain\n", "\n", - "path = '../data/example_meristem.ply'\n", + "path = 'tutorial_data/domains/example_meristem.ply'\n", "\n", "cd = CustomDomain.read(path)\n", "\n", @@ -419,7 +419,7 @@ "from bvpy.utils.visu import plot\n", "from bvpy.domains import CustomPolygonalDomain\n", "\n", - "path ='../data/tissue_example_curved_big.txt'\n", + "path ='tutorial_data/domains/tissue_example_curved_big.txt'\n", "\n", "tissue = CustomPolygonalDomain.read(path)\n", "\n", diff --git a/tutorials/bvpy_tutorial_3_vforms.ipynb b/tutorials/bvpy_tutorial_3_vforms.ipynb index 459e89c16fe6b788d35d016cc0367ff5dfa50488..17250c8c88c4b325cdfb4fd0376db283f79c808e 100644 --- a/tutorials/bvpy_tutorial_3_vforms.ipynb +++ b/tutorials/bvpy_tutorial_3_vforms.ipynb @@ -16,7 +16,7 @@ "\n", "- Implementation of *de novo* `vform`: Definition of the variational formulation of a differential equation, instanciation of mandatory `@staticmethods`: `.construct_form()` and `.set_expression`.\n", "\n", - "Download the notebook: [bvpy_tutorial_3](https://gitlab.inria.fr/mosaic/publications/bvpy/-/raw/develop/tutorials/bvpy_tutorial_3_vforms.ipynb?inline=false)." + "Download the tutorial notebooks and accompanying data as a ZIP file: [tutorials.zip](https://mosaic.gitlabpages.inria.fr/bvpy/tutorials.zip)." ] }, { @@ -321,7 +321,7 @@ "from bvpy.utils.io import save\n", "\n", "plot(prblm.solution)\n", - "save(prblm.solution, '../data/tutorial_results/tuto_3_result_1.xdmf')" + "save(prblm.solution, 'tutorial_data/tuto_3_result_1.xdmf')" ] } ], diff --git a/tutorials/bvpy_tutorial_4_bnd_cond.ipynb b/tutorials/bvpy_tutorial_4_bnd_cond.ipynb index 941388edef2808ab5f3a16125f8be6124dc884ff..c20c581cd4192354f5548a2a5595eeb145395cc4 100644 --- a/tutorials/bvpy_tutorial_4_bnd_cond.ipynb +++ b/tutorials/bvpy_tutorial_4_bnd_cond.ipynb @@ -23,7 +23,7 @@ "\n", "- Example of visualization of boundary conditions when a bvp is defined.\n", "\n", - "Download the notebook: [bvpy_tutorial_4](https://gitlab.inria.fr/mosaic/publications/bvpy/-/raw/develop/tutorials/bvpy_tutorial_4_bnd_cond.ipynb?inline=false)." + "Download the tutorial notebooks and accompanying data as a ZIP file: [tutorials.zip](https://mosaic.gitlabpages.inria.fr/bvpy/tutorials.zip)." ] }, { diff --git a/tutorials/bvpy_tutorial_5_reaction_diffusion.ipynb b/tutorials/bvpy_tutorial_5_reaction_diffusion.ipynb index 864116f6a5237f55d022e961166618da92ca3564..21188ed3e4dd8b7a1ec68620ee109f350b9bcd99 100644 --- a/tutorials/bvpy_tutorial_5_reaction_diffusion.ipynb +++ b/tutorials/bvpy_tutorial_5_reaction_diffusion.ipynb @@ -25,7 +25,7 @@ "\n", "\n", "\n", - "Download the notebook: [bvpy_tutorial_5](https://gitlab.inria.fr/mosaic/publications/bvpy/-/raw/develop/tutorials/bvpy_tutorial_5_turing_system.ipynb?inline=false)." + "Download the tutorial notebooks and accompanying data as a ZIP file: [tutorials.zip](https://mosaic.gitlabpages.inria.fr/bvpy/tutorials.zip)." ] }, { @@ -152,16 +152,16 @@ ] }, { - "cell_type": "code", - "execution_count": null, "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "t_min = 0\n", "t_max = 29\n", "dt = 0.1\n", "\n", - "wave_propagation.integrate(t_min, t_max, dt, '../data/tutorial_results/tuto_5_result_1.xdmf', store_steps=True)" + "wave_propagation.integrate(t_min, t_max, dt, 'tutorial_data/tuto_5_result_1.xdmf', store_steps=True)" ] }, { @@ -188,14 +188,14 @@ ] }, { - "cell_type": "code", - "execution_count": null, "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "from IPython.display import Image\n", "\n", - "Image(filename='../data/tutorial_results/tuto_5_result_1_paraview_snapshot.gif.png')" + "Image(filename='tutorial_data/results/tuto_5_result_1_paraview_snapshot.gif.png')" ] }, { @@ -463,16 +463,16 @@ ] }, { - "cell_type": "code", - "execution_count": null, "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "t_min = 0\n", "t_max = 9\n", "dt = 0.1\n", "\n", - "save_path = '../data/tutorial_results/tuto_5_result_2.xdmf'\n", + "save_path = 'tutorial_data/tuto_5_result_2.xdmf'\n", "\n", "turing_system.integrate(t_min, t_max, dt, save_path)" ] @@ -522,16 +522,16 @@ ] }, { - "cell_type": "code", - "execution_count": null, "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "from IPython.display import Image\n", "\n", - "Image(filename='../data/tutorial_results/tuto_5_result_2_paraview_snapshot.gif.png')\n", + "Image(filename='tutorial_data/results/tuto_5_result_2_paraview_snapshot.gif.png')\n", "#from IPython.display import HTML\n", - "#HTML('<img src=\"../data/tutorial_results/tuto_5_result_2_paraview_snapshot.gif\">')" + "#HTML('<img src=\"../tutorial_data/tutorial_results/tuto_5_result_2_paraview_snapshot.gif\">')" ] } ], diff --git a/tutorials/bvpy_tutorial_6_linear_elasticity.ipynb b/tutorials/bvpy_tutorial_6_linear_elasticity.ipynb index bc3b1a80d432132175830978119d3cb0c207ec33..1625e8a77ec1e146978a37e5af8b4e4fee86bfc2 100644 --- a/tutorials/bvpy_tutorial_6_linear_elasticity.ipynb +++ b/tutorials/bvpy_tutorial_6_linear_elasticity.ipynb @@ -25,7 +25,7 @@ "\n", "\n", "\n", - "Download the notebook: [bvpy_tutorial_6](https://gitlab.inria.fr/mosaic/publications/bvpy/-/raw/develop/tutorials/bvpy_tutorial_6_linear_elasticity.ipynb?inline=false)." + "Download the tutorial notebooks and accompanying data as a ZIP file: [tutorials.zip](https://mosaic.gitlabpages.inria.fr/bvpy/tutorials.zip)." ] }, { @@ -273,7 +273,7 @@ "source": [ "from bvpy.utils.io import save\n", "\n", - "path = '../data/tutorial_results/tuto_6_result_1.xdmf'\n", + "path = 'tutorial_data/tuto_6_result_1.xdmf'\n", "save(stretching, path)" ], "outputs": [], @@ -285,7 +285,7 @@ "source": [ "from IPython.display import Image\n", "\n", - "Image(filename='../data/tutorial_results/tuto_6_result_1_paraview_snapshot.png')" + "Image(filename='tutorial_data/results/tuto_6_result_1_paraview_snapshot.png')" ], "outputs": [], "execution_count": null @@ -410,7 +410,7 @@ "source": [ "wound_displacement = wound_healing.solution\n", "plot(wound_displacement, size=1)\n", - "path = '../data/tutorial_results/tuto_6_result_2.xdmf'\n", + "path = 'tutorial_data/tuto_6_result_2.xdmf'\n", "save(wound_healing, path)" ], "outputs": [], @@ -468,18 +468,14 @@ { "cell_type": "code", "metadata": {}, - "source": [ - "save(stress, '../data/tutorial_results/tuto_6_result_3.xdmf')" - ], + "source": "save(stress, 'tutorial_data/tuto_6_result_3.xdmf')", "outputs": [], "execution_count": null }, { "cell_type": "code", "metadata": {}, - "source": [ - "Image(filename=\"../data/tutorial_results/tuto_6_result_3_paraview_snapshot.png\")" - ], + "source": "Image(filename=\"tutorial_data/results/tuto_6_result_3_paraview_snapshot.png\")", "outputs": [], "execution_count": null }, @@ -508,7 +504,7 @@ "from bvpy.domains import CustomPolygonalDomain\n", "from bvpy.utils.visu import plot\n", "\n", - "tissue = CustomPolygonalDomain.read(\"../data/example_domain_sepal_2D_slice.ply\",\n", + "tissue = CustomPolygonalDomain.read(\"tutorial_data/domains/example_domain_sepal_2D_slice.ply\",\n", " cell_size=.5,\n", " dim=2)\n", "tissue.discretize()\n", @@ -581,7 +577,7 @@ "displacement = prblm.solution\n", "#plot(displacement, size=1, norm=True)\n", "\n", - "save(displacement, '../data/tutorial_results/tuto_6_result_4.xdmf')" + "save(displacement, 'tutorial_data/tuto_6_result_4.xdmf')" ], "outputs": [], "execution_count": null @@ -610,8 +606,8 @@ "metadata": {}, "source": [ "stress = heterogeneous_elastic_response.get_stress(displacement)\n", - "save(stress, \"../data/tutorial_results/tuto_6_result_5.xdmf\")\n", - "Image(filename=\"../data/tutorial_results/tuto_6_result_5_paraview_snapshot.png\")" + "save(stress, \"tutorial_data/tuto_6_result_5.xdmf\")\n", + "Image(filename=\"tutorial_data/results/tuto_6_result_5_paraview_snapshot.png\")" ], "outputs": [], "execution_count": null @@ -820,9 +816,9 @@ "cell_type": "code", "metadata": {}, "source": [ - "fixed_center = [dirichlet(0, boundary='near(x, 0)', subspace=0),\n", - " dirichlet(0, boundary='near(y, 0)', subspace=1),\n", - " dirichlet(0, boundary='near(z, 0)', subspace=2)]" + "fixed_center = [dirichlet(0, boundary='near(x, 0)', subspace=0, method='pointwise'),\n", + " dirichlet(0, boundary='near(y, 0)', subspace=1, method='pointwise'),\n", + " dirichlet(0, boundary='near(z, 0)', subspace=2, method='pointwise')]" ], "outputs": [], "execution_count": null @@ -851,7 +847,7 @@ "source": [ "displacement = pressurized_shell.solution\n", "\n", - "save(displacement, '../data/tutorial_results/tuto_6_result_6.xdmf')\n", + "save(displacement, 'tutorial_data/tuto_6_result_6.xdmf')\n", "\n", "plot(displacement, size=.25, norm=True)" ], @@ -880,9 +876,7 @@ { "cell_type": "code", "metadata": {}, - "source": [ - "tissue = CustomPolygonalDomain.read('../data/example_domain_curved_cellularized_tissue.ply')" - ], + "source": "tissue = CustomPolygonalDomain.read('tutorial_data/domains/example_domain_curved_cellularized_tissue.ply')", "outputs": [], "execution_count": null }, @@ -958,7 +952,7 @@ "source": [ "displacement = prblm.solution\n", "\n", - "save(displacement, '../data/tutorial_results/tuto_6_result_7.xdmf')\n", + "save(displacement, 'tutorial_data/tuto_6_result_7.xdmf')\n", "\n", "plot(displacement, size=1000, norm=True)" ], diff --git a/tutorials/bvpy_tutorial_7_hyper_elasticity.ipynb b/tutorials/bvpy_tutorial_7_hyper_elasticity.ipynb index 820ce379d2b3fd275caa5567f34d8bcd063c678b..48b9fb1c98c5a6818378218dd6bea27144eac138 100644 --- a/tutorials/bvpy_tutorial_7_hyper_elasticity.ipynb +++ b/tutorials/bvpy_tutorial_7_hyper_elasticity.ipynb @@ -21,7 +21,7 @@ "\n", "\n", "\n", - "Download the notebook: [bvpy_tutorial_7](https://gitlab.inria.fr/mosaic/publications/bvpy/-/raw/develop/tutorials/bvpy_tutorial_7_hyper_elasticity.ipynb?inline=false)." + "Download the tutorial notebooks and accompanying data as a ZIP file: [tutorials.zip](https://mosaic.gitlabpages.inria.fr/bvpy/tutorials.zip)." ] }, { @@ -684,7 +684,7 @@ " t_max = kwargs.get('t_max',2.5)\n", " dt = kwargs.get('dt',.1)\n", " \n", - " save_path = kwargs.get('saving_path', '../data/tutorial_results/tuto_7_result_1.xdmf')\n", + " save_path = kwargs.get('saving_path', 'tutorial_data/tuto_7_result_1.xdmf')\n", " \n", " # -- Instancing Turing model\n", " LE_model = CoupledTransportForm()\n", @@ -770,7 +770,7 @@ " stress = compute_mecha_equi(concentrations)\n", " concentrations = compute_turing_pattern(concentrations)\n", "\n", - " save(stress, f\"../data/tutorial_results/tuto_7_result_{1+step}.xdmf\")" + " save(stress, f\"tutorial_data/tuto_7_result_{1+step}.xdmf\")" ], "outputs": [], "execution_count": null @@ -795,7 +795,7 @@ "source": [ "from IPython.display import Image\n", "\n", - "Image(filename=\"../data/tutorial_results/tuto_7_result_3_paraview_snapshot.gif.png\")" + "Image(filename=\"tutorial_data/results/tuto_7_result_3_paraview_snapshot.gif.png\")" ], "outputs": [], "execution_count": null diff --git a/tutorials/bvpy_tutorial_8_pyvista.ipynb b/tutorials/bvpy_tutorial_8_pyvista.ipynb index 553ecd8a7b4b15b63dae7a9134eac7f4aab10415..3e40b020ae43f3e893c96a4b1210b991f49ac57e 100644 --- a/tutorials/bvpy_tutorial_8_pyvista.ipynb +++ b/tutorials/bvpy_tutorial_8_pyvista.ipynb @@ -1,15 +1,17 @@ { "cells": [ { - "cell_type": "markdown", - "id": "ce32e709", "metadata": {}, + "cell_type": "markdown", "source": [ "\n", "# Visualization with `PyVista`\n", "\n", - "This tutorial explores the use of `PyVista` for visualizing various objects within the `bvpy` library. While previous visualizations in `bvpy` primarily used `Plotly`, this tutorial demonstrates how `PyVista` can enhance the visualization experience. Users can still use `Plotly` if preferred; however, `PyVista` provides specific advantages in handling complex 3D meshes and volumetric data more efficiently.\n" - ] + "This tutorial explores the use of `PyVista` for visualizing various objects within the `bvpy` library. While previous visualizations in `bvpy` primarily used `Plotly`, this tutorial demonstrates how `PyVista` can enhance the visualization experience. Users can still use `Plotly` if preferred; however, `PyVista` provides specific advantages in handling complex 3D meshes and volumetric data more efficiently.\n", + "\n", + "Download the tutorial notebooks and accompanying data as a ZIP file: [tutorials.zip](https://mosaic.gitlabpages.inria.fr/bvpy/tutorials.zip).\n" + ], + "id": "c57d32315ff947c" }, { "metadata": {}, @@ -28,19 +30,19 @@ "\n", "*Note: Visualization of tensor fields is currently not supported but will be introduced in a future release.*" ], - "id": "1d939304979bb125" + "id": "a316fe34f491896a" }, { - "cell_type": "code", - "id": "e8ec604b7f241a60", "metadata": {}, + "cell_type": "code", + "outputs": [], + "execution_count": null, "source": [ "import pyvista as pv\n", "import math\n", "from bvpy.utils.visu_pyvista import visualize" ], - "outputs": [], - "execution_count": null + "id": "afbf7befc1a34a8b" }, { "cell_type": "markdown", @@ -54,8 +56,10 @@ }, { "cell_type": "code", + "execution_count": null, "id": "6fd0fb4b-7f5d-4979-af33-ef96041d75cc", "metadata": {}, + "outputs": [], "source": [ "from bvpy.domains.primitives import Cylinder\n", "\n", @@ -66,9 +70,7 @@ "cylinder = Cylinder(x=0, dx=L, dz=0, r=r, cell_type='triangle',\n", " cell_size=.1, algorithm='Frontal-Delaunay', clear=True)\n", "cylinder.discretize() # discretize (from Gmsh -> FeniCS)" - ], - "outputs": [], - "execution_count": null + ] }, { "cell_type": "markdown", @@ -80,29 +82,31 @@ }, { "cell_type": "code", + "execution_count": null, "id": "45c8981941e15904", "metadata": {}, + "outputs": [], "source": [ "visualize(cylinder, visu_type='mesh')" - ], - "outputs": [], - "execution_count": null + ] }, { "cell_type": "markdown", "id": "9eb5b9c3fd496d6d", "metadata": {}, - "source": "Notice that it is possible to give directly the `FeniCS` Mesh object" + "source": [ + "Notice that it is possible to give directly the `FeniCS` Mesh object" + ] }, { "cell_type": "code", + "execution_count": null, "id": "b43a12b3-005d-4d2e-afc8-ce69eb00df43", "metadata": {}, + "outputs": [], "source": [ "visualize(cylinder.mesh, visu_type='mesh', window_size=[600, 400]) # give the Mesh object and control the window size" - ], - "outputs": [], - "execution_count": null + ] }, { "cell_type": "markdown", @@ -117,12 +121,17 @@ { "metadata": {}, "cell_type": "markdown", - "source": "As an example, we will define a custom geometry called `CutCylinder`. This geometry represents a cylinder that is divided into two distinct subdomains. For detailed steps on generating this geometry, refer to the tutorial [Generate advanced geometries using Gmsh](bvpy_tutorial_9_gmsh).", - "id": "497835f2c378930a" + "source": [ + "\n", + "As an example, we will define a custom geometry called `CutCylinder`. This geometry represents a cylinder that is divided into two distinct subdomains. For detailed steps on generating this geometry, refer to the tutorial [Generate advanced geometries using Gmsh](bvpy_tutorial_9_gmsh.html)." + ], + "id": "dc47158816d1ea4d" }, { "metadata": {}, "cell_type": "code", + "outputs": [], + "execution_count": null, "source": [ "from bvpy.domains.abstract import OccModel, AbstractDomain\n", "\n", @@ -156,19 +165,22 @@ " self.model.addPhysicalGroup(3, [right_surface], tag=2)\n", " self.model.setPhysicalName(3, 2, \"Right_Subdomain\")" ], - "id": "99e8589b7c70f8f9", - "outputs": [], - "execution_count": null + "id": "6547c0d4549458df" }, { - "metadata": {}, "cell_type": "markdown", - "source": "We can now initialize and visualize an instance of the `CutCylinder` class, which represents the custom geometry defined above.", - "id": "674698ebdbd01f11" + "id": "674698ebdbd01f11", + "metadata": {}, + "source": [ + "We can now initialize and visualize an instance of the `CutCylinder` class, which represents the custom geometry defined above." + ] }, { - "metadata": {}, "cell_type": "code", + "execution_count": null, + "id": "6b6950b42f835af5", + "metadata": {}, + "outputs": [], "source": [ "cut_cylinder = CutCylinder(length=L, radius=r,\n", " cell_type='tetra', algorithm='Frontal-Delaunay',\n", @@ -176,53 +188,60 @@ "cut_cylinder.discretize()\n", "\n", "print(f\"The available sub-domains are {cut_cylinder.sub_domain_names}\")" - ], - "id": "6b6950b42f835af5", - "outputs": [], - "execution_count": null + ] }, { - "metadata": {}, "cell_type": "code", - "source": "visualize(cut_cylinder, visu_type='domain')", + "execution_count": null, "id": "be0836fa9004f4fb", + "metadata": {}, "outputs": [], - "execution_count": null + "source": [ + "visualize(cut_cylinder, visu_type='domain')" + ] }, { - "metadata": {}, "cell_type": "markdown", - "source": "Notice that it is possible to give directly the `FeniCS` MeshSizet object", - "id": "66f4f0ee9e579385" + "id": "66f4f0ee9e579385", + "metadata": {}, + "source": [ + "Notice that it is possible to give directly the `FeniCS` MeshSizet object" + ] }, { "cell_type": "code", + "execution_count": null, "id": "a5be726f73bf8fa0", "metadata": {}, - "source": "visualize(cut_cylinder.cdata, visu_type='domain', show_edges=False) # give the MeshSizet object and control the display of the edges", "outputs": [], - "execution_count": null + "source": [ + "visualize(cut_cylinder.cdata, visu_type='domain', show_edges=False) # give the MeshSizet object and control the display of the edges" + ] }, { "cell_type": "markdown", "id": "f5d23d182bc6da05", "metadata": {}, - "source": "If you directly provide the `FeniCS` `MeshSizet` object, you can retrieve the sub-domain names by using the optional argument `dict_values`:" + "source": [ + "If you directly provide the `FeniCS` `MeshSizet` object, you can retrieve the sub-domain names by using the optional argument `dict_values`:" + ] }, { - "metadata": {}, "cell_type": "code", - "source": "visualize(cut_cylinder.cdata, visu_type='domain', dict_values=cut_cylinder.sub_domain_names)", + "execution_count": null, "id": "8c9d36506e6a17ac", + "metadata": {}, "outputs": [], - "execution_count": null + "source": [ + "visualize(cut_cylinder.cdata, visu_type='domain', dict_values=cut_cylinder.sub_domain_names)" + ] }, { "cell_type": "markdown", "id": "9bf14848-0641-4826-b4a2-d9f760344d79", "metadata": {}, "source": [ - "### Diriclet boundary conditions\n", + "### Dirichlet boundary conditions\n", "\n", "In this section, we will visualize the Dirichlet boundary conditions to verify that they are correctly applied in the problem setup.\n", "We will use a simple linear elastic problem where the previously defined cylinder is stretched at its ends." @@ -230,8 +249,10 @@ }, { "cell_type": "code", + "execution_count": null, "id": "40f911dc-78ae-4b73-bd6d-65af44d5ed44", "metadata": {}, + "outputs": [], "source": [ "from bvpy.boundary_conditions import dirichlet, Boundary\n", "from bvpy.utils.pre_processing import HeterogeneousParameter\n", @@ -252,9 +273,7 @@ "\n", "### - Build the problem - ###\n", "elastic_pb = BVP(cut_cylinder, elastic_model, bc1+bc2)" - ], - "outputs": [], - "execution_count": null + ] }, { "cell_type": "markdown", @@ -266,13 +285,13 @@ }, { "cell_type": "code", + "execution_count": null, "id": "def488a2833689cc", "metadata": {}, + "outputs": [], "source": [ "visualize(elastic_pb, visu_type='dirichlet', scale=0.1, val_range=[0, 5])" - ], - "outputs": [], - "execution_count": null + ] }, { "cell_type": "markdown", @@ -286,36 +305,36 @@ }, { "cell_type": "code", + "execution_count": null, "id": "7793c8bac4680bf9", "metadata": {}, + "outputs": [], "source": [ "# Available parameters from the weak form\n", "print(f\"Available parameters from the weak form : {list(elastic_pb.vform._parameters.keys())}\")" - ], - "outputs": [], - "execution_count": null + ] }, { "cell_type": "code", + "execution_count": null, "id": "467cf8b5-7433-4bac-a347-6f9f38f28360", "metadata": {}, + "outputs": [], "source": [ "visualize(elastic_pb, visu_type='young')" - ], - "outputs": [], - "execution_count": null + ] }, { "cell_type": "code", + "execution_count": null, "id": "ad63e001-dd9c-448b-93cc-6a6e379b753f", "metadata": {}, + "outputs": [], "source": [ "# Do the same for the poisson parameter but change the camera view of the pyvista.Plotter\n", "pl = visualize(elastic_pb, visu_type='poisson') # get the pyvisya.Plotter\n", "pl.view_xz() # change the camera view" - ], - "outputs": [], - "execution_count": null + ] }, { "cell_type": "markdown", @@ -329,30 +348,30 @@ }, { "cell_type": "code", + "execution_count": null, "id": "d3d84515-748e-4f9b-8e12-c2a5d6e0f05c", "metadata": {}, + "outputs": [], "source": [ "# - Solve the previous elastic problem\n", "elastic_pb.solve()\n", "\n", "# - Visualize the resulting displacement field\n", "visualize(elastic_pb, 'solution', scale=0.1)" - ], - "outputs": [], - "execution_count": null + ] }, { "cell_type": "code", + "execution_count": null, "id": "b965cf59-d94e-4b74-b711-3e3468f37480", "metadata": {}, + "outputs": [], "source": [ "print(type(elastic_pb.solution))\n", "\n", "# - You can also visualize any FeniCS Function\n", "visualize(elastic_pb.solution, scale=0.1, show_mesh=False) # show directly the FeniCS Function & remove mesh" - ], - "outputs": [], - "execution_count": null + ] }, { "cell_type": "markdown", @@ -364,14 +383,14 @@ }, { "cell_type": "code", + "execution_count": null, "id": "7e77fab2-6171-4cbb-a093-348545cd9b0a", "metadata": {}, + "outputs": [], "source": [ "pl = visualize(elastic_pb.solution, scale=0.1, show_mesh=False, show_plot=False) # show the FeniCS Function without the mesh\n", "visualize(elastic_pb.domain.mesh, plotter=pl, actor_kwargs={'style':'wireframe', 'line_width': 2}) # add the mesh as a 'wireframe' & specify the edge size" - ], - "outputs": [], - "execution_count": null + ] }, { "cell_type": "markdown", @@ -397,8 +416,10 @@ }, { "cell_type": "code", + "execution_count": null, "id": "354f8ef0-2f33-44ac-a077-f451c9abe24b", "metadata": {}, + "outputs": [], "source": [ "# - Some parameters\n", "young = {1: 0.5, 2: 1.5}\n", @@ -417,14 +438,14 @@ "\n", "# - Compute the deformed mesh\n", "deformed_cylinder = cut_cylinder.move(bvp.solution, return_cdata=False) # create a copy of the object & apply displacement field" - ], - "outputs": [], - "execution_count": null + ] }, { "cell_type": "code", + "execution_count": null, "id": "60f96a29-b395-45d3-974c-bd74b5819cd2", "metadata": {}, + "outputs": [], "source": [ "# - Define a pv.Plotter with 6 subplots (shape = (nrow, ncol))\n", "pl = pv.Plotter(shape=(2, 3), window_size=[1000, 800])\n", @@ -451,9 +472,7 @@ "\n", "pl.link_views() # link the camera of the subplots\n", "pl.show() # finally show the plot" - ], - "outputs": [], - "execution_count": null + ] }, { "cell_type": "markdown", @@ -467,7 +486,9 @@ "cell_type": "markdown", "id": "1cd028f30cc62ca9", "metadata": {}, - "source": "### Visualize sub-space of `FeniCS` object" + "source": [ + "### Visualize sub-space of `FeniCS` object" + ] }, { "cell_type": "markdown", @@ -479,15 +500,15 @@ }, { "cell_type": "code", + "execution_count": null, "id": "b8b0e47b3808d048", "metadata": {}, + "outputs": [], "source": [ "stress = vform.get_stress(bvp.solution)\n", "\n", "print(f\"The stress field is a tensor field of shape {stress.ufl_shape}\")" - ], - "outputs": [], - "execution_count": null + ] }, { "cell_type": "markdown", @@ -499,17 +520,17 @@ }, { "cell_type": "code", + "execution_count": null, "id": "145fc3ce7d3ce05c", "metadata": {}, + "outputs": [], "source": [ "SCALAR_BAR_ARGS = dict(title_font_size=12,\n", " label_font_size=10,\n", " n_labels=3,\n", " italic=True,\n", " fmt=\"%.1e\")" - ], - "outputs": [], - "execution_count": null + ] }, { "cell_type": "markdown", @@ -521,8 +542,10 @@ }, { "cell_type": "code", + "execution_count": null, "id": "c5619019b6d87755", "metadata": {}, + "outputs": [], "source": [ "dict_sub = {'xx': 0, 'yy': 4, 'zz': 8} # FeniCS subspace index are organized in 1D --> xx, xy, xz, yx, yy, yz, zx, zy, zz\n", "\n", @@ -534,21 +557,23 @@ "\n", "pl.link_views()\n", "pl.show()" - ], - "outputs": [], - "execution_count": null + ] }, { "cell_type": "markdown", "id": "c7a5faacfdbc7609", "metadata": {}, - "source": "### Use `Pyvista` widgets to enhance visualization options" + "source": [ + "### Use `Pyvista` widgets to enhance visualization options" + ] }, { "cell_type": "markdown", "id": "ce8999330d5618e4", "metadata": {}, - "source": "Various `Pyvista` widgets can be used to help the visualization of `FeniCS` object. Feel free to consult the widgets from https://docs.pyvista.org/examples/03-widgets/index.html." + "source": [ + "Various `Pyvista` widgets can be used to help the visualization of `FeniCS` object. Feel free to consult the widgets from https://docs.pyvista.org/examples/03-widgets/index.html." + ] }, { "cell_type": "markdown", @@ -560,16 +585,16 @@ }, { "cell_type": "code", + "execution_count": null, "id": "ed38c8e729addf54", "metadata": {}, + "outputs": [], "source": [ "pl = visualize(bvp.solution, show_plot=False, show_mesh=False, show_scalar_bar=False, actor_kwargs={'show_glyph': False}) # do not show mesh & glyph (arrows)\n", "pl.add_mesh_clip_plane(pl.meshes[0], invert=True, assign_to_axis='y') # then retrieve the mesh and add a clip plane. Assign to one axis (can be deactivated)\n", "pl.add_title('Displacement field\\n(with clip plane)', font_size=10)\n", "pl.show()" - ], - "outputs": [], - "execution_count": null + ] } ], "metadata": { diff --git a/tutorials/bvpy_tutorial_9_gmsh.ipynb b/tutorials/bvpy_tutorial_9_gmsh.ipynb index 83dcf7d6e80db3179c0532f37d9a11300d68b4dd..a663263dceef6c52ecf4df7cbce943f9f4bffeb4 100644 --- a/tutorials/bvpy_tutorial_9_gmsh.ipynb +++ b/tutorials/bvpy_tutorial_9_gmsh.ipynb @@ -21,7 +21,10 @@ "\n", "> https://koehlerson.github.io/gmsh.jl/dev/examples/t1/\n", "\n", - "> https://gmsh.info/doc/texinfo/gmsh.html#Gmsh-tutorial" + "> https://gmsh.info/doc/texinfo/gmsh.html#Gmsh-tutorial\n", + "\n", + "\n", + "Download the tutorial notebooks and accompanying data as a ZIP file: [tutorials.zip](https://mosaic.gitlabpages.inria.fr/bvpy/tutorials.zip)." ] }, { diff --git a/data/example_domain_curved_cellularized_tissue.ply b/tutorials/tutorial_data/domains/example_domain_curved_cellularized_tissue.ply similarity index 100% rename from data/example_domain_curved_cellularized_tissue.ply rename to tutorials/tutorial_data/domains/example_domain_curved_cellularized_tissue.ply diff --git a/data/example_domain_sepal_2D_slice.ply b/tutorials/tutorial_data/domains/example_domain_sepal_2D_slice.ply similarity index 100% rename from data/example_domain_sepal_2D_slice.ply rename to tutorials/tutorial_data/domains/example_domain_sepal_2D_slice.ply diff --git a/data/example_meristem.ply b/tutorials/tutorial_data/domains/example_meristem.ply similarity index 100% rename from data/example_meristem.ply rename to tutorials/tutorial_data/domains/example_meristem.ply diff --git a/data/tissue_example_curved_big.txt b/tutorials/tutorial_data/domains/tissue_example_curved_big.txt similarity index 100% rename from data/tissue_example_curved_big.txt rename to tutorials/tutorial_data/domains/tissue_example_curved_big.txt diff --git a/data/tutorial_results/tuto_5_result_1_paraview_snapshot.gif.png b/tutorials/tutorial_data/results/tuto_5_result_1_paraview_snapshot.gif.png similarity index 100% rename from data/tutorial_results/tuto_5_result_1_paraview_snapshot.gif.png rename to tutorials/tutorial_data/results/tuto_5_result_1_paraview_snapshot.gif.png diff --git a/data/tutorial_results/tuto_5_result_2_paraview_snapshot.gif.png b/tutorials/tutorial_data/results/tuto_5_result_2_paraview_snapshot.gif.png similarity index 100% rename from data/tutorial_results/tuto_5_result_2_paraview_snapshot.gif.png rename to tutorials/tutorial_data/results/tuto_5_result_2_paraview_snapshot.gif.png diff --git a/data/tutorial_results/tuto_6_result_1_paraview_snapshot.png b/tutorials/tutorial_data/results/tuto_6_result_1_paraview_snapshot.png similarity index 100% rename from data/tutorial_results/tuto_6_result_1_paraview_snapshot.png rename to tutorials/tutorial_data/results/tuto_6_result_1_paraview_snapshot.png diff --git a/data/tutorial_results/tuto_6_result_3_paraview_snapshot.png b/tutorials/tutorial_data/results/tuto_6_result_3_paraview_snapshot.png similarity index 100% rename from data/tutorial_results/tuto_6_result_3_paraview_snapshot.png rename to tutorials/tutorial_data/results/tuto_6_result_3_paraview_snapshot.png diff --git a/data/tutorial_results/tuto_6_result_5_paraview_snapshot.png b/tutorials/tutorial_data/results/tuto_6_result_5_paraview_snapshot.png similarity index 100% rename from data/tutorial_results/tuto_6_result_5_paraview_snapshot.png rename to tutorials/tutorial_data/results/tuto_6_result_5_paraview_snapshot.png diff --git a/data/tutorial_results/tuto_7_result_3_paraview_snapshot.gif.png b/tutorials/tutorial_data/results/tuto_7_result_3_paraview_snapshot.gif.png similarity index 100% rename from data/tutorial_results/tuto_7_result_3_paraview_snapshot.gif.png rename to tutorials/tutorial_data/results/tuto_7_result_3_paraview_snapshot.gif.png