diff --git a/.github/workflows/pychaste-conda-tests.yml b/.github/workflows/pychaste-conda-tests.yml new file mode 100644 index 0000000000..bd0381f5c0 --- /dev/null +++ b/.github/workflows/pychaste-conda-tests.yml @@ -0,0 +1,90 @@ +name: PyChaste conda tests + +on: + workflow_dispatch: + pull_request: + branches: + - "**" + +# Limit concurrent runs to one per branch +concurrency: + group: pychaste-conda-tests-${{ github.ref }} + cancel-in-progress: true + +jobs: + pychaste-conda-tests: + if: ${{ !contains(github.event.pull_request.labels.*.name, 'ci:off') }} + + strategy: + fail-fast: false + matrix: + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + + runs-on: ubuntu-latest + + env: + CHASTE_TEST_OUTPUT: ${{ github.workspace }}/testoutput + + defaults: + run: + shell: bash -el {0} # -l needed to activate conda + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Make build and test directories + run: | + mkdir -p build + mkdir -p ${CHASTE_TEST_OUTPUT} + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y castxml clang cmake xvfb + + - name: Setup Miniconda Python ${{ matrix.python-version }} + uses: conda-incubator/setup-miniconda@v3 + with: + auto-update-conda: true + use-mamba: true + miniforge-version: latest + environment-file: pychaste/src/py/conda/environment.yml + python-version: ${{ matrix.python-version }} + channels: conda-forge,pychaste + + - name: Configure PyChaste + run: | + cmake \ + -DChaste_ENABLE_PYCHASTE=ON \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX="${CONDA_PREFIX}" \ + -DCMAKE_PREFIX_PATH="${CONDA_PREFIX}" \ + -DCMAKE_LIBRARY_PATH="${CONDA_PREFIX}/lib" \ + -DBUILD_SHARED_LIBS=ON \ + -DBOOST_ROOT="${CONDA_PREFIX}" \ + -DHDF5_C_COMPILER_EXECUTABLE="${CONDA_PREFIX}/bin/h5pcc" \ + -DPETSC_DIR="${CONDA_PREFIX}" \ + -DPYTHON_EXECUTABLE="$(which python)" \ + -DVTK_DIR="${CONDA_PREFIX}" \ + -DXERCESC_INCLUDE="${CONDA_PREFIX}/include" \ + -DXERCESC_LIBRARY="${CONDA_PREFIX}/lib/libxerces-c.so" \ + -DXSD_EXECUTABLE="${CONDA_PREFIX}/bin/xsd" \ + .. + working-directory: build + + - name: Build PyChaste + run: | + make -j $(nproc) pychaste + working-directory: build + + - name: Install PyChaste + run: | + python3 -m pip install -v pychaste/package + working-directory: build + + - name: Run PyChaste tests + run: | + xvfb-run --server-args="-screen 0 1024x768x24" \ + ctest -j $(nproc) -L pychaste --output-on-failure + working-directory: build diff --git a/.github/workflows/pychaste-ubuntu-tests.yml b/.github/workflows/pychaste-ubuntu-tests.yml new file mode 100644 index 0000000000..ad428bfb72 --- /dev/null +++ b/.github/workflows/pychaste-ubuntu-tests.yml @@ -0,0 +1,89 @@ +name: PyChaste Ubuntu tests + +on: + workflow_dispatch: + pull_request: + branches: + - "**" + +# Limit concurrent runs to one per branch +concurrency: + group: pychaste-ubuntu-tests-${{ github.ref }} + cancel-in-progress: true + +jobs: + pychaste-ubuntu-tests: + if: ${{ !contains(github.event.pull_request.labels.*.name, 'ci:off') }} + + strategy: + fail-fast: false + matrix: + os: ["ubuntu-22.04", "ubuntu-24.04"] + + runs-on: ${{ matrix.os }} + + env: + CHASTE_TEST_OUTPUT: ${{ github.workspace }}/testoutput + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Make build and test directories + run: | + mkdir -p build + mkdir -p ${CHASTE_TEST_OUTPUT} + + - name: Install Chaste dependencies + run: | + codename=$(cat /etc/os-release | grep VERSION_CODENAME | cut -d= -f2) + sudo wget -O /usr/share/keyrings/chaste.asc https://chaste.github.io/chaste.asc + echo "deb [signed-by=/usr/share/keyrings/chaste.asc] https://chaste.github.io/ubuntu ${codename}/" \ + | sudo tee -a /etc/apt/sources.list.d/chaste.list + sudo apt-get update + sudo apt-get install -y chaste-dependencies + + - name: Install extra PyChaste dependencies + run: | + for vtk_ver in $(seq 7 9); do + installed=1 + dpkg -s "libvtk${vtk_ver}-dev" || installed=0 + if [ "${installed}" -eq 1 ]; then + break + fi + done + + sudo apt-get install -y \ + castxml \ + clang \ + python3-matplotlib \ + python3-mpi4py \ + python3-notebook \ + python3-numpy \ + python3-petsc4py-real \ + python3-pip \ + python3-vtk${vtk_ver} \ + python3-xvfbwrapper \ + xvfb + + - name: Configure PyChaste + run: | + cmake -DCMAKE_BUILD_TYPE=Release -DChaste_ENABLE_PYCHASTE=ON .. + working-directory: build + + - name: Build PyChaste + run: | + make -j $(nproc) pychaste + working-directory: build + + - name: Install PyChaste + run: | + PIP_BREAK_SYSTEM_PACKAGES=1 \ + python3 -m pip install -v --user pychaste/package + working-directory: build + + - name: Run PyChaste tests + run: | + xvfb-run --server-args="-screen 0 1024x768x24" \ + ctest -j $(nproc) -L pychaste --output-on-failure + working-directory: build diff --git a/.github/workflows/update-pychaste-tutorials.yml b/.github/workflows/update-pychaste-tutorials.yml new file mode 100644 index 0000000000..9708d4fbf7 --- /dev/null +++ b/.github/workflows/update-pychaste-tutorials.yml @@ -0,0 +1,77 @@ +name: Update pychaste tutorials + +on: + pull_request: + branches: + - '**' + push: + branches: + - develop + workflow_dispatch: + +jobs: + + generate-tutorial-markdown: + runs-on: ubuntu-latest + if: ${{ !contains(github.event.pull_request.labels.*.name, 'ci:off') }} + steps: + - name: checkout Chaste + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch all history in order to get revision info for tutorial files + + - name: setup python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: install dependencies + run: | + python -m pip install jupyterlab + + - name: generate pychaste tutorial markdown + run: | + python GeneratePyChasteTutorials.py + working-directory: python/infra + + - name: upload archive files + uses: actions/upload-artifact@v4 + with: + name: pychaste-tutorial-markdown + path: | + pychaste/src/py/doc/tutorial/*.md + + upload-to-website: + needs: generate-tutorial-markdown + runs-on: ubuntu-latest + if: github.event_name == 'push' + steps: + - name: Download pychaste-tutorial-markdown + uses: actions/download-artifact@v4 + with: + name: pychaste-tutorial-markdown + path: pychaste/src/py/doc/tutorial + + - name: Checkout website repository + uses: actions/checkout@v4 + with: + repository: Chaste/Chaste.github.io + token: ${{ secrets.WEBSITE_ACCESS }} + path: chaste-website + fetch-depth: 0 # Fetch all history for all tags and branches + + - name: Display structure of downloaded files + run: ls -R + + - name: Copy markdown to website repository + run: | + cp pychaste/src/py/doc/tutorial/*.md chaste-website/site/content/pychaste/tutorials + + - name: Commit and push if changed + run: | + git config user.name "github-action" + git config user.email "github-action" + git add --all + git commit -m "update pychaste tutorials" || echo "No changes to commit" + git push + working-directory: chaste-website diff --git a/.gitignore b/.gitignore index 8256b90f15..ccdd7d01bb 100644 --- a/.gitignore +++ b/.gitignore @@ -25,7 +25,7 @@ _local/ # Temporary build folders for dynamically loaded models /heart/dynamic/tmp_*_* /heart/dynamic/luo_rudy_1991_dyn.* -/codegen_python3_venv +/chaste_python3_venv **/__pycache__/ # Local hostconfig diff --git a/CMakeLists.txt b/CMakeLists.txt index 2bae9d56f2..3fe94d8b6c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -184,6 +184,8 @@ option (Chaste_INSTALL_TESTS "Install Chaste Testing infrastructure" OFF) set (Chaste_CODEGEN_EXTRA_ARGS "" CACHE STRING "Add any extra arguments for chaste_codegen here") +option (Chaste_ENABLE_PYCHASTE "Enable PyChaste" OFF) + #Some Chaste-specific #defines add_definitions (-DCHASTE_CMAKE) @@ -273,18 +275,23 @@ endif () ################################ #### Find Python ################################ -find_package(Python3 3.5) +if (Chaste_ENABLE_PYCHASTE) + find_package(Python3 3.5 COMPONENTS Interpreter Development) +else () + find_package(Python3 3.5) +endif () + if (Python3_VERSION VERSION_LESS 3.5.0) message(FATAL_ERROR "Python 3.5 or higher is required; found v${Python3_VERSION} instead. Specify interpreter with -DPython_EXECUTABLE=/path/to/python3") endif () # Install chaste_codegen in virtual environment -set(codegen_python3_venv ${PROJECT_BINARY_DIR}/codegen_python3_venv/bin) +set(chaste_python3_venv ${PROJECT_BINARY_DIR}/chaste_python3_venv/bin) -if (NOT EXISTS ${codegen_python3_venv}) - message (STATUS "Creating virtual environment for chaste_codegen in ${PROJECT_BINARY_DIR}/codegen_python3_venv") +if (NOT EXISTS ${chaste_python3_venv}) + message (STATUS "Creating virtual environment for chaste_codegen in ${PROJECT_BINARY_DIR}/chaste_python3_venv") execute_process( - COMMAND ${Python3_EXECUTABLE} -m venv codegen_python3_venv + COMMAND ${Python3_EXECUTABLE} -m venv chaste_python3_venv WORKING_DIRECTORY ${PROJECT_BINARY_DIR} ) endif () @@ -292,21 +299,51 @@ endif () # Update pip setuptools and wheel message (STATUS "Updating pip setuptools and wheel") if (${Python3_VERSION} VERSION_LESS 3.6.0) - execute_process(COMMAND ${codegen_python3_venv}/pip install --no-cache-dir "pip>=20.0, <21.0") - execute_process(COMMAND ${codegen_python3_venv}/pip install --no-cache-dir --upgrade setuptools wheel) + execute_process(COMMAND ${chaste_python3_venv}/pip install --no-cache-dir "pip>=20.0, <21.0") + execute_process(COMMAND ${chaste_python3_venv}/pip install --no-cache-dir --upgrade setuptools wheel) else() - execute_process(COMMAND ${codegen_python3_venv}/pip install --no-cache-dir --upgrade pip setuptools wheel) + execute_process(COMMAND ${chaste_python3_venv}/pip install --no-cache-dir --upgrade pip setuptools wheel) endif() # Install chaste_codegen in virtual environment message (STATUS "Installing chaste_codegen") -execute_process(COMMAND ${codegen_python3_venv}/pip install --no-cache-dir --upgrade -r ${CMAKE_SOURCE_DIR}/chaste_codegen.txt) -execute_process(COMMAND ${codegen_python3_venv}/chaste_codegen --version +execute_process(COMMAND ${chaste_python3_venv}/pip install --no-cache-dir --upgrade -r ${CMAKE_SOURCE_DIR}/chaste_codegen.txt) +execute_process(COMMAND ${chaste_python3_venv}/chaste_codegen --version OUTPUT_VARIABLE Chaste_CODEGEN_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE) message("Codegen version: ${Chaste_CODEGEN_VERSION}") +# Get pychaste dependencies +if (Chaste_ENABLE_PYCHASTE) + set(Chaste_USE_VTK ON) + + # Get pybind11 + include(FetchContent) + FetchContent_Declare( + pybind11 + GIT_REPOSITORY https://github.com/pybind/pybind11 + GIT_TAG stable + GIT_SHALLOW 1 + ) + FetchContent_MakeAvailable(pybind11) + + # Install C++ wrapper generator (cppwg) in venv + message (STATUS "Installing C++ wrapper generator") + FetchContent_Declare( + cppwg + GIT_REPOSITORY https://github.com/Chaste/cppwg.git + GIT_TAG stable + GIT_SHALLOW 1 + ) + FetchContent_MakeAvailable(cppwg) + + execute_process(COMMAND ${chaste_python3_venv}/pip install ${cppwg_SOURCE_DIR}) + execute_process(COMMAND ${chaste_python3_venv}/cppwg --version + OUTPUT_VARIABLE Chaste_CPPWG_VERSION + OUTPUT_STRIP_TRAILING_WHITESPACE) + message("CPPWG version: ${Chaste_CPPWG_VERSION}") +endif () if (Chaste_ENABLE_TESTING AND Chaste_ACCEPTANCE) find_package (TextTest) @@ -363,19 +400,47 @@ if (Chaste_USE_VTK) endif () # Depending on the VTK version, we need different components. + set (_vtk_modules "") if (VTK_MAJOR_VERSION LESS 9) - find_package( - VTK COMPONENTS vtkCommonCore vtkCommonDataModel vtkFiltersCore vtkFiltersGeneral vtkFiltersGeneric - vtkFiltersGeometry vtkFiltersModeling vtkFiltersSources vtkIOCore vtkIOGeometry vtkIOLegacy - vtkIOParallelXML vtkIOXML REQUIRED - ) - # VTK 9.0 and above require the following + list (APPEND _vtk_modules + vtkCommonCore vtkCommonDataModel vtkFiltersCore + vtkFiltersGeneral vtkFiltersGeneric vtkFiltersGeometry + vtkFiltersModeling vtkFiltersSources vtkIOCore vtkIOGeometry + vtkIOLegacy vtkIOParallelXML vtkIOXML + ) + if (Chaste_ENABLE_PYCHASTE) + # Additional modules required by PyChaste + list (APPEND _vtk_modules + vtkInteractionStyle vtkIOImage vtkIOMovie + vtkRenderingAnnotation vtkRenderingCore vtkRenderingFreeType + vtkWrappingPythonCore + ) + if (VTK_MAJOR_VERSION LESS 7) + list (APPEND _vtk_modules vtkRenderingOpenGL) + else () + list (APPEND _vtk_modules + vtkFiltersProgrammable vtkFiltersVerdict + vtkRenderingOpenGL2 + ) + endif () + endif () else () - find_package( - VTK COMPONENTS CommonCore CommonDataModel FiltersCore FiltersGeneral FiltersGeneric FiltersGeometry - FiltersModeling FiltersSources IOCore IOGeometry IOLegacy IOParallelXML IOXML REQUIRED - ) + # VTK 9.0 and above require the following + list (APPEND _vtk_modules + CommonCore CommonDataModel FiltersCore FiltersGeneral + FiltersGeneric FiltersGeometry FiltersModeling FiltersSources + IOCore IOGeometry IOLegacy IOParallelXML IOXML + ) + if (Chaste_ENABLE_PYCHASTE) + # Additional modules required by PyChaste + list (APPEND _vtk_modules + FiltersProgrammable FiltersVerdict InteractionStyle IOImage + IOMovie IOOggTheora RenderingAnnotation RenderingCore + RenderingFreeType RenderingOpenGL2 WrappingPythonCore + ) + endif () endif () + find_package (VTK COMPONENTS ${_vtk_modules} REQUIRED) add_definitions (-DCHASTE_VTK) endif () @@ -404,6 +469,10 @@ endif () ################################ #### Find PETSc ################################ +if (Chaste_ENABLE_PYCHASTE) + find_package(PETSc4py REQUIRED) +endif () + find_package (PETSc REQUIRED) list (APPEND Chaste_LINK_LIBRARIES "${PETSC_LIBRARIES}") @@ -590,6 +659,10 @@ message ("####################################\n") # List the available Chaste components set (Chaste_COMPONENTS global io linalg mesh ode pde continuum_mechanics cell_based crypt) +if (Chaste_ENABLE_PYCHASTE) + list (APPEND Chaste_COMPONENTS pychaste) +endif () + if (NOT (WIN32 OR CYGWIN)) set (Chaste_COMPONENTS ${Chaste_COMPONENTS} lung heart) endif () @@ -673,6 +746,9 @@ set (Chaste_DEPENDS_crypt cell_based pde ode mesh linalg io global) set (Chaste_DEPENDS_continuum_mechanics pde ode mesh linalg io global) set (Chaste_DEPENDS_heart ${Chaste_DEPENDS_continuum_mechanics} continuum_mechanics) set (Chaste_DEPENDS_lung ${Chaste_DEPENDS_continuum_mechanics} continuum_mechanics) +if (Chaste_ENABLE_PYCHASTE) + set (Chaste_DEPENDS_pychaste cell_based) +endif () # These custom targets depend on other components but are not components themselves set (Chaste_DEPENDS_core global io linalg mesh ode pde continuum_mechanics) diff --git a/apps/texttest/chaste/config.chaste b/apps/texttest/chaste/config.chaste index 2331361abc..91773209c4 100644 --- a/apps/texttest/chaste/config.chaste +++ b/apps/texttest/chaste/config.chaste @@ -178,7 +178,7 @@ output:/[^ ]+/(Chaste|[_a-zA-Z0-9]+-[-_0-9]+){REPLACE } output:(/[^ ]*/)?heart/[^ ]*dynamic/tmp_[0-9]+_[0-9]+{REPLACE } output:RunXsd\(\[.*\], \[(.*)\]\){REPLACE Run Xsd on \1} output:[^ ]*mpicxx .*(/[^/]+\.[a-zA-Z]+){REPLACE mpicxx \1} -output:Running \['.*/codegen_python3_venv/bin/chaste_codegen', '-A', '-p', '--output-dir', '/', '-y', '/(.*).cellml'\]{REPLACE Running chaste_codegen on \1} +output:Running \['.*/chaste_python3_venv/bin/chaste_codegen', '-A', '-p', '--output-dir', '/', '-y', '/(.*).cellml'\]{REPLACE Running chaste_codegen on \1} output:scons: building associated output:scons: Reading SConscript files ...{LINES 10} diff --git a/cmake/Modules/ChasteMacros.cmake b/cmake/Modules/ChasteMacros.cmake index ae61ab5b2b..7b8921b440 100644 --- a/cmake/Modules/ChasteMacros.cmake +++ b/cmake/Modules/ChasteMacros.cmake @@ -75,7 +75,7 @@ macro(Chaste_DO_CELLML output_sources cellml_file dynamic) endif() set(depends ${cellml_dir}/${cellml_file_name}.cellml) - execute_process(COMMAND "${codegen_python3_venv}/chaste_codegen" ${codegen_args} ${Chaste_CODEGEN_EXTRA_ARGS} --show-outputs ${cellml_file} + execute_process(COMMAND "${chaste_python3_venv}/chaste_codegen" ${codegen_args} ${Chaste_CODEGEN_EXTRA_ARGS} --show-outputs ${cellml_file} OUTPUT_VARIABLE ConvertCellModelDepends OUTPUT_STRIP_TRAILING_WHITESPACE ) @@ -88,7 +88,7 @@ macro(Chaste_DO_CELLML output_sources cellml_file dynamic) endif() add_custom_command(OUTPUT ${output_files_hpp} ${output_files_cpp} - COMMAND "${codegen_python3_venv}/chaste_codegen" ${codegen_args} ${Chaste_CODEGEN_EXTRA_ARGS} ${cellml_file} + COMMAND "${chaste_python3_venv}/chaste_codegen" ${codegen_args} ${Chaste_CODEGEN_EXTRA_ARGS} ${cellml_file} DEPENDS ${depends} COMMENT "Processing CellML file ${cellml_file_rel}" WORKING_DIRECTORY ${PROJECT_BINARY_DIR} diff --git a/cmake/Modules/FindPETSc4py.cmake b/cmake/Modules/FindPETSc4py.cmake new file mode 100644 index 0000000000..42a0792096 --- /dev/null +++ b/cmake/Modules/FindPETSc4py.cmake @@ -0,0 +1,30 @@ +# - Try to find petsc4py +# Once done this will define +# +# PETSC4PY_FOUND - system has petsc4py +# PETSC4PY_INCLUDES - the petsc4py include directories +# PETSC4PY_VERSION - version string (MAJOR.MINOR.SUBMINOR) +# +# Usage: find_package(PETSc4py) + +execute_process( + COMMAND ${Python3_EXECUTABLE} -c "import petsc4py; print(petsc4py.get_include(), end='')" + OUTPUT_VARIABLE PETSC4PY_INCLUDES +) + +if(PETSC4PY_INCLUDES) + execute_process( + COMMAND ${Python3_EXECUTABLE} -c "import petsc4py; print(petsc4py.__version__, end='')" + OUTPUT_VARIABLE PETSC4PY_VERSION + ) +endif() + +mark_as_advanced(PETSC4PY_INCLUDES, PETSC4PY_VERSION) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + PETSc4py + REQUIRED_VARS PETSC4PY_INCLUDES + VERSION_VAR PETSC4PY_VERSION + FAIL_MESSAGE "PETSc4py could not be found." +) diff --git a/docs/licencing/Licences.html b/docs/licencing/Licences.html index b622345647..a631bbb2ad 100644 --- a/docs/licencing/Licences.html +++ b/docs/licencing/Licences.html @@ -74,6 +74,13 @@

Third party packages

Tests, Distributed + + dolfinx + https://github.com/FEniCS/dolfinx/ + dolfinx.txt + Source, PyChaste + + HDF5 http://www.hdfgroup.org/products/licenses.html @@ -126,6 +133,13 @@

Third party packages

Source, Cardiac + + SMTK + https://github.com/Kitware/SMTK/ + SMTK.txt + Source, PyChaste + + TetGen http://tetgen.berlios.de/ @@ -133,6 +147,13 @@

Third party packages

Source, Distributed + + three.js + https://github.com/mrdoob/three.js/ + three.js.txt + Source, PyChaste + + triangle http://www.cs.cmu.edu/~quake/triangle.html diff --git a/docs/licencing/licences/SMTK.txt b/docs/licencing/licences/SMTK.txt new file mode 100644 index 0000000000..0f0d7b888a --- /dev/null +++ b/docs/licencing/licences/SMTK.txt @@ -0,0 +1,38 @@ +SMTK License Version 1.0 +======================================================================== +Copyright (c) 2012 Kitware Inc. 28 Corporate Drive +Clifton Park, NY, 12065, USA. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of Kitware nor the names of any contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The following files and directories come from third parties. Check the +contents of these for details on the specifics of their respective +licenses. +- - - - - - - - - - - - - - - - - - - - - - - - +Thirdparty/pugixml +Thirdparty/PyYaml +Thidpary/rtvl +CMake/FindDocutils.cmake diff --git a/docs/licencing/licences/dolfinx.txt b/docs/licencing/licences/dolfinx.txt new file mode 100644 index 0000000000..491226c85a --- /dev/null +++ b/docs/licencing/licences/dolfinx.txt @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General 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. diff --git a/docs/licencing/licences/three.js.txt b/docs/licencing/licences/three.js.txt new file mode 100644 index 0000000000..d33709fbf0 --- /dev/null +++ b/docs/licencing/licences/three.js.txt @@ -0,0 +1,21 @@ +The MIT License + +Copyright © 2010-2024 three.js authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/heart/src/odes/CellMLToSharedLibraryConverter.cpp b/heart/src/odes/CellMLToSharedLibraryConverter.cpp index dc6bfa2785..f91fe83d4d 100644 --- a/heart/src/odes/CellMLToSharedLibraryConverter.cpp +++ b/heart/src/odes/CellMLToSharedLibraryConverter.cpp @@ -204,7 +204,7 @@ void CellMLToSharedLibraryConverter::ConvertCellmlToSo(const std::string& rCellm "set(CMAKE_CXX_EXTENSIONS OFF)\n" << "project (ChasteCellMLToSharedLibraryConverter)\n" << "find_package(Python3 3.5 REQUIRED)\n" << - "set(codegen_python3_venv " + chaste_root.GetAbsolutePath() + "/codegen_python3_venv/bin)\n" << + "set(chaste_python3_venv " + chaste_root.GetAbsolutePath() + "/chaste_python3_venv/bin)\n" << "find_package(Chaste COMPONENTS " << mComponentName << ")\n" << "chaste_do_cellml(sources " << cellml_file.GetAbsolutePath() << " " << "ON " << codegen_args << ")\n" << "set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})\n" << diff --git a/heart/test/ionicmodels/TestCodegenPresent.hpp b/heart/test/ionicmodels/TestCodegenPresent.hpp index 7acb325f3e..ec7075aa93 100644 --- a/heart/test/ionicmodels/TestCodegenPresent.hpp +++ b/heart/test/ionicmodels/TestCodegenPresent.hpp @@ -54,8 +54,8 @@ class TestCodegenPresent : public CxxTest::TestSuite public: void TestChaste_codegenInstalled() { - FileFinder codegen("codegen_python3_venv/bin/chaste_codegen", RelativeTo::ChasteBuildRoot); - FileFinder python("codegen_python3_venv/bin/python", RelativeTo::ChasteBuildRoot); + FileFinder codegen("chaste_python3_venv/bin/chaste_codegen", RelativeTo::ChasteBuildRoot); + FileFinder python("chaste_python3_venv/bin/python", RelativeTo::ChasteBuildRoot); TS_ASSERT_EQUALS(codegen.IsFile(), true); TS_ASSERT_EQUALS(python.IsFile(), true); diff --git a/pychaste/.gitignore b/pychaste/.gitignore new file mode 100644 index 0000000000..7573bd4599 --- /dev/null +++ b/pychaste/.gitignore @@ -0,0 +1,2 @@ +# Jupyter notebooks +.ipynb_checkpoints/ diff --git a/pychaste/CMakeLists.txt b/pychaste/CMakeLists.txt new file mode 100644 index 0000000000..c1a7831785 --- /dev/null +++ b/pychaste/CMakeLists.txt @@ -0,0 +1,149 @@ +# Copyright (c) 2005-2024, University of Oxford. +# All rights reserved. +# +# University of Oxford means the Chancellor, Masters and Scholars of the +# University of Oxford, having an administrative office at Wellington +# Square, Oxford OX1 2JD, UK. +# +# This file is part of Chaste. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of the University of Oxford nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +if(NOT Chaste_ENABLE_PYCHASTE) + return() +endif() + +find_package(Chaste COMPONENTS ${Chaste_DEPENDS_pychaste}) + +chaste_do_component(pychaste) + +################################ +#### Generate Python wrappers +################################ + +# Get includes from `chaste_pychaste` (target for non-wrapper code in pychaste/src/cpp) +get_target_property(PYCHASTE_INCLUDE_DIRS chaste_pychaste INCLUDE_DIRECTORIES) + +# Add includes for VTK 9+ (includes for VTK < 9 will already be in the list) +if(TARGET VTK::CommonCore) + get_target_property(_include_dirs VTK::CommonCore INTERFACE_INCLUDE_DIRECTORIES) + list(APPEND PYCHASTE_INCLUDE_DIRS ${_include_dirs}) +endif() + +# Add PETSC4Py includes +list(APPEND PYCHASTE_INCLUDE_DIRS ${PETSC4PY_INCLUDES}) + +# Add includes for pybind11 typecasters +header_dirs("${CMAKE_CURRENT_SOURCE_DIR}/dynamic" _include_dirs) +list(APPEND PYCHASTE_INCLUDE_DIRS ${_include_dirs}) + +# Command for generating wrappers +set(PYCHASTE_WRAPPER_GENERATION_COMMAND + ${chaste_python3_venv}/cppwg ${CMAKE_SOURCE_DIR} + -w ${CMAKE_CURRENT_BINARY_DIR}/wrappers + -p ${CMAKE_CURRENT_SOURCE_DIR}/dynamic/config.yaml + -i ${PYCHASTE_INCLUDE_DIRS} + -l ${CMAKE_CURRENT_BINARY_DIR}/cppwg.log + --std c++17 +) + +# Generate wrappers if not already created +if(NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/wrappers") + execute_process(COMMAND ${PYCHASTE_WRAPPER_GENERATION_COMMAND}) +endif() + +# Target for manually re-generating the wrappers +add_custom_target(pychaste_wrappers + COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_CURRENT_BINARY_DIR}/wrappers + COMMAND ${PYCHASTE_WRAPPER_GENERATION_COMMAND} +) + +# Add generated wrappers to includes +header_dirs("${CMAKE_CURRENT_BINARY_DIR}/wrappers" _include_dirs) +list(APPEND PYCHASTE_INCLUDE_DIRS ${_include_dirs}) + +################################ +#### Build Python module +################################ +# Creates a `_pychaste_all` shared library from the Python wrappers. +# The library name `_pychaste_all` is the same as the module name in the main +# module wrapper source file `wrappers/all/_pychaste_all.main.cppwg.cpp`. +# When the shared library has been built, it can be used in Python code +# as `from chaste._pychaste_all import *`. + +file(GLOB_RECURSE PYCHASTE_WRAPPER_SOURCES + ${CMAKE_CURRENT_BINARY_DIR}/wrappers/all/*.cpp + ${CMAKE_CURRENT_BINARY_DIR}/wrappers/all/*.hpp +) + +pybind11_add_module(_pychaste_all SHARED ${PYCHASTE_WRAPPER_SOURCES}) + +target_link_libraries(_pychaste_all PRIVATE + ${Python3_LIBRARIES} + Chaste_COMMON_DEPS + chaste_pychaste +) + +target_include_directories(_pychaste_all PRIVATE ${PYCHASTE_INCLUDE_DIRS}) + +target_compile_options(_pychaste_all PRIVATE -flto=auto -Wno-unused-local-typedefs) + +set_target_properties(_pychaste_all PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/package/chaste +) + +add_dependencies(pychaste _pychaste_all) + +################################ +#### Copy Python package +################################ + +# Get list of files in the Python package directory: pychaste/src/py/ +set(package_file_paths "") +file( + GLOB_RECURSE + package_file_paths + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/src/py + ${CMAKE_CURRENT_SOURCE_DIR}/src/py/* +) + +# Exclude compiled files +list(FILTER package_file_paths EXCLUDE REGEX ".*\\.(dll|dylib|pyc|so)$") + +# Copy files to the build directory +foreach(file_path ${package_file_paths}) + if(file_path MATCHES ".*\\.(cfg|in|js|py|toml)$") + # Copy file and track changes + configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/src/py/${file_path} + ${CMAKE_CURRENT_BINARY_DIR}/package/${file_path} + COPYONLY + ) + else() + # Copy file, but don't track changes + file( + COPY ${CMAKE_CURRENT_SOURCE_DIR}/src/py/${file_path} + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/package/ + ) + endif() +endforeach() diff --git a/pychaste/dynamic/config.yaml b/pychaste/dynamic/config.yaml new file mode 100644 index 0000000000..fd354526b2 --- /dev/null +++ b/pychaste/dynamic/config.yaml @@ -0,0 +1,1062 @@ +name: pychaste + +smart_ptr_type: boost::shared_ptr +pointer_call_policy: reference +reference_call_policy: reference_internal + +common_include_file: OFF + +source_includes: + - + - + - + - + - SmartPointers.hpp + +template_substitutions: + - signature: + replacement: [[2], [3]] + - signature: + replacement: [[2], [3]] + - signature: + replacement: [[2, 2], [3, 3]] + - signature: + replacement: [[2, 2], [3, 3]] + - signature: + replacement: [[2, 2], [3, 3]] + - signature: + replacement: + - [Alarcon2004OxygenBasedCellCycleModel, 2] + - [Alarcon2004OxygenBasedCellCycleModel, 3] + - [AlwaysDivideCellCycleModel, 2] + - [AlwaysDivideCellCycleModel, 3] + - [BernoulliTrialCellCycleModel, 2] + - [BernoulliTrialCellCycleModel, 3] + - [BiasedBernoulliTrialCellCycleModel, 2] + - [BiasedBernoulliTrialCellCycleModel, 3] + - [ContactInhibitionCellCycleModel, 2] + - [ContactInhibitionCellCycleModel, 3] + - [ExponentialG1GenerationalCellCycleModel, 2] + - [ExponentialG1GenerationalCellCycleModel, 3] + - [FixedG1GenerationalCellCycleModel, 2] + - [FixedG1GenerationalCellCycleModel, 3] + - [FixedSequenceCellCycleModel, 2] + - [FixedSequenceCellCycleModel, 3] + - [GammaG1CellCycleModel, 2] + - [GammaG1CellCycleModel, 3] + - [LabelDependentBernoulliTrialCellCycleModel, 2] + - [LabelDependentBernoulliTrialCellCycleModel, 3] + - [NoCellCycleModel, 2] + - [NoCellCycleModel, 3] + - [SimpleOxygenBasedCellCycleModel, 2] + - [SimpleOxygenBasedCellCycleModel, 3] + - [StochasticOxygenBasedCellCycleModel, 2] + - [StochasticOxygenBasedCellCycleModel, 3] + - [TysonNovakCellCycleModel, 2] + - [TysonNovakCellCycleModel, 3] + - [UniformCellCycleModel, 2] + - [UniformCellCycleModel, 3] + - [UniformG1GenerationalCellCycleModel, 2] + - [UniformG1GenerationalCellCycleModel, 3] + +modules: + - name: all + source_locations: + ##=== cell_based + - cell_based/src + - pychaste/src/cpp/cell_based + ##=== core + - global/src + ##=== mesh + - mesh/src + - cell_based/src/mesh + ##=== ode + - ode/src + - cell_based/src/odes + ##=== pde + - pde/src + - cell_based/src/cell_based_pde + ##=== visualization + - pychaste/src/cpp/visualization + + classes: + ##=== core: Chaste/global/src + - name: RelativeTo # enum + source_file: FileFinder.hpp + + - name: FileFinder + + - name: OutputFileHandler + source_includes: + - FileFinder.hpp # forward decl + excluded_methods: + - OpenOutputFile # std io not wrapped + + - name: ProgressReporter + - name: RandomNumberGenerator + - name: TimeStepper + + - name: ChasteBuildInfo + source_file: Version.hpp + + ##=== core: Chaste/global/src/checkpointing + - name: Identifiable + + ##=== core: Chaste/global/src/fortests + - name: PetscSetupUtils + + ##=== core: Chaste/global/src/parallel + - name: PetscTools + excluded_methods: + - GetWorld # MPI not wrapped here + - Destroy # No non-const ref to PETSc types + - ReadPetscObject + - SetupMat + excluded_variables: + - MASTER_RANK # MPI not wrapped here + source_includes: + - PybindPetscTypeCaster.hpp + + - name: ReplicatableVector + custom_generator: "CPPWG_SOURCEROOT/pychaste/dynamic/templates/ReplicatableVectorCustomTemplate.py" + source_includes: + - PybindPetscTypeCaster.hpp + + ##=== core: Chaste/global/src/timing + - name: Timer + + ##=== ode: Chaste/ode/src/common + - name: AbstractOdeSystemInformation + - name: AbstractOdeSystem + + ##=== ode: Chaste/cell_based/src/ode + - name: DeltaNotchOdeSystem + - name: DeltaNotchEdgeOdeSystem + - name: DeltaNotchInteriorOdeSystem + - name: Alarcon2004OxygenBasedCellCycleOdeSystem + - name: Goldbeter1991OdeSystem + + - name: TysonNovak2001OdeSystem + excluded_methods: + - AnalyticJacobian # double ** jacobian + + - name: CellwiseOdeSystemInformation + excluded: True + + #=== pde: Chaste/pde/src/common + - name: AbstractBoundaryCondition + - name: ConstBoundaryCondition + - name: PdeSimulationTime + + #=== pde: Chaste/pde/src/problem + - name: AbstractLinearPde + - name: AbstractLinearParabolicPde + - name: AbstractLinearEllipticPde + + - name: AbstractLinearParabolicPdeSystemForCoupledOdeSystem + template_substitutions: + - signature: + + replacement: [[2, 2, 1], [3, 3, 1]] + source_includes: + - PybindUblasTypeCaster.hpp + + - name: AbstractNonlinearEllipticPde + + #=== pde: Chaste/cell_based/src/cell_based_pde/pdes + - name: CellwiseSourceEllipticPde + - name: AveragedSourceEllipticPde + - name: VolumeDependentAveragedSourceEllipticPde + - name: UniformSourceEllipticPde + - name: CellwiseSourceParabolicPde + - name: UniformSourceParabolicPde + - name: AveragedSourceParabolicPde + + #=== pde: Chaste/cell_based/src/cell_based_pde + - name: CellBasedEllipticPdeSolver + source_includes: + - PybindPetscTypeCaster.hpp + - PybindUblasTypeCaster.hpp + + - name: CellBasedParabolicPdeSolver + source_includes: + - PybindUblasTypeCaster.hpp + + - name: AbstractPdeModifier + excluded_methods: + - GetSolution + - GetFeMesh + source_includes: + - PybindPetscTypeCaster.hpp + + - name: AbstractBoxDomainPdeModifier + source_includes: + - PybindPetscTypeCaster.hpp + + - name: AbstractGrowingDomainPdeModifier + source_includes: + - PybindPetscTypeCaster.hpp + + - name: EllipticGrowingDomainPdeModifier + source_includes: + - PybindPetscTypeCaster.hpp + excluded_methods: + - ConstructBoundaryConditionsContainer + + - name: ParabolicGrowingDomainPdeModifier + source_includes: + - PybindPetscTypeCaster.hpp + excluded_methods: + - ConstructBoundaryConditionsContainer + + - name: EllipticBoxDomainPdeModifier + source_includes: + - PybindPetscTypeCaster.hpp + excluded_methods: + - ConstructBoundaryConditionsContainer + + - name: ParabolicBoxDomainPdeModifier + source_includes: + - PybindPetscTypeCaster.hpp + excluded_methods: + - ConstructBoundaryConditionsContainer + + #=== mesh: Chaste/mesh/src/common + - name: ChastePoint + source_includes: + - PybindUblasTypeCaster.hpp + + - name: AbstractChasteRegion + + - name: AbstractElement + template_substitutions: + - signature: + replacement: [[1, 2], [2, 2], [2, 3], [3, 3]] + source_includes: + - PybindUblasTypeCaster.hpp + + - name: AbstractMesh + source_includes: + - PybindUblasTypeCaster.hpp + + - name: NodeAttributes + source_includes: + - PybindUblasTypeCaster.hpp + + - name: Node + source_includes: + - PybindUblasTypeCaster.hpp + + - name: Edge + source_includes: + - PybindUblasTypeCaster.hpp + + - name: EdgeHelper + - name: EdgeOperation + + - name: Element + excluded_methods: + - CalculateCircumsphereVolume # method not found! + source_includes: + - PybindUblasTypeCaster.hpp + + - name: MutableElement + template_substitutions: + - signature: + replacement: [[1, 2], [2, 2], [2, 3], [3, 3]] + custom_generator: "CPPWG_SOURCEROOT/pychaste/dynamic/templates/MutableElementCustomTemplate.py" + constructor_signature_excludes: + - [unsigned] + + - name: AbstractTetrahedralMesh + source_includes: + - PybindUblasTypeCaster.hpp + + - name: TetrahedralMesh + excluded_methods: + - FreeTriangulateIo + - InitialiseTriangulateIo + source_includes: + - PybindUblasTypeCaster.hpp + + - name: MutableMesh + excluded_methods: + - SplitLongEdges # can't work with vec + - RescaleMeshFromBoundaryNode # method not found! + source_includes: + - PybindUblasTypeCaster.hpp + + - name: NodesOnlyMesh + source_includes: + - PybindUblasTypeCaster.hpp + + #=== mesh: Chaste/mesh/src/immersed_boundary + - name: FluidSource + source_includes: + - PybindUblasTypeCaster.hpp + + - name: ImmersedBoundaryElement + template_substitutions: + - signature: + replacement: [[1, 2], [2, 2], [2, 3], [3, 3]] + excluded_methods: AddCornerNode # not implemented in 1D + + - name: ImmersedBoundaryMesh + source_includes: + - PybindUblasTypeCaster.hpp + + - name: ImmersedBoundaryHoneycombMeshGenerator + source_includes: + - PybindUblasTypeCaster.hpp + + - name: ImmersedBoundaryPalisadeMeshGenerator + + #=== mesh: Chaste/mesh/src/mutable + - name: Cylindrical2dMesh + excluded_methods: + - GetInstanceOfMismatchedBoundaryNodes + source_includes: + - PybindUblasTypeCaster.hpp + + - name: Cylindrical2dNodesOnlyMesh + excluded_methods: + - SetUpBoxCollection # boost::numeric::ublas::unit_vector + source_includes: + - PybindUblasTypeCaster.hpp + + - name: PeriodicNodesOnlyMesh + excluded_methods: + - SetUpBoxCollection # boost::numeric::ublas::zero_vector + source_includes: + - PybindUblasTypeCaster.hpp + + - name: Toroidal2dMesh + excluded_methods: + - GetInstanceOfMismatchedBoundaryNodes + source_includes: + - PybindUblasTypeCaster.hpp + + - name: HoneycombMeshGenerator + - name: CylindricalHoneycombMeshGenerator + - name: ToroidalHoneycombMeshGenerator + + #=== mesh: Chaste/mesh/src/utilities + - name: ChasteCuboid + - name: ChasteEllipsoid + + #=== mesh: Chaste/mesh/src/vertex + - name: VertexMesh + excluded_methods: + - GetFace # dont have mixed dim elements + - rGetEdgeHelper + constructor_arg_type_excludes: + - TetrahedralMesh<3, 3> + - TetrahedralMesh<2, 2> + source_includes: + - PybindUblasTypeCaster.hpp + + - name: MutableVertexMesh + source_includes: + - PybindUblasTypeCaster.hpp + + - name: Cylindrical2dVertexMesh + source_includes: + - PybindUblasTypeCaster.hpp + + - name: Toroidal2dVertexMesh + source_includes: + - PybindUblasTypeCaster.hpp + + - name: HoneycombVertexMeshGenerator + - name: CylindricalHoneycombVertexMeshGenerator + - name: ToroidalHoneycombVertexMeshGenerator + - name: VoronoiVertexMeshGenerator + + #=== mesh: Chaste/cell_based/src/mesh + - name: PottsElement + + - name: PottsMesh + source_includes: + - PybindUblasTypeCaster.hpp + + - name: PottsMeshGenerator + excluded_methods: + - CaclulateNeighbouringNodeIndices # not implemented + + - name: PottsMeshWriter + + - name: PottsMeshReader + excluded: True + + - name: PottsElementData + excluded: True + + ##=== cell_based: Chaste/cell_based/src/cell + - name: Cell + excluded_methods: + - rGetCellPropertyCollection + - GetSrnModel + + - name: CellsGenerator + custom_generator: "CPPWG_SOURCEROOT/pychaste/dynamic/templates/CellsGeneratorCustomTemplate.py" + source_includes: + - NoCellCycleModel.hpp + - UniformCellCycleModel.hpp + - SimpleOxygenBasedCellCycleModel.hpp + - UniformG1GenerationalCellCycleModel.hpp + - BiasedBernoulliTrialCellCycleModel.hpp + - LabelDependentBernoulliTrialCellCycleModel.hpp + - AlwaysDivideCellCycleModel.hpp + - ContactInhibitionCellCycleModel.hpp + - StochasticOxygenBasedCellCycleModel.hpp + - GammaG1CellCycleModel.hpp + - ExponentialG1GenerationalCellCycleModel.hpp + - TysonNovakCellCycleModel.hpp + - Alarcon2004OxygenBasedCellCycleModel.hpp + - FixedSequenceCellCycleModel.hpp + - BernoulliTrialCellCycleModel.hpp + - FixedG1GenerationalCellCycleModel.hpp + + - name: ParallelCellsGenerator + excluded: True + + - name: null_deleter + excluded: True + + ##=== cell_based: Chaste/cell_based/src/cell/cycle + - name: AbstractCellCycleModel + - name: AbstractPhaseBasedCellCycleModel + - name: AbstractSimpleCellCycleModel + - name: AbstractSimplePhaseBasedCellCycleModel + - name: AbstractSimpleGenerationalCellCycleModel + - name: AbstractCellCycleModelOdeSolver + - name: AbstractOdeBasedCellCycleModel + - name: AbstractOdeBasedPhaseBasedCellCycleModel + - name: NoCellCycleModel + - name: UniformCellCycleModel + - name: UniformG1GenerationalCellCycleModel + - name: SimpleOxygenBasedCellCycleModel + - name: StochasticOxygenBasedCellCycleModel + - name: BiasedBernoulliTrialCellCycleModel + - name: LabelDependentBernoulliTrialCellCycleModel + - name: AlwaysDivideCellCycleModel + - name: ContactInhibitionCellCycleModel + - name: GammaG1CellCycleModel + - name: ExponentialG1GenerationalCellCycleModel + - name: TysonNovakCellCycleModel + - name: Alarcon2004OxygenBasedCellCycleModel + - name: FixedSequenceCellCycleModel + - name: BernoulliTrialCellCycleModel + - name: FixedG1GenerationalCellCycleModel + + - name: CellCycleModelOdeSolver + excluded: True + + - name: CellCycleModelOdeHandler + excluded: True + + - name: CellCycleTimesGenerator + excluded: True + + ##=== cell_based: Chaste/cell_based/src/cell_based/properties + - name: AbstractCellProperty + excluded_methods: + - IsType # exclude templated method + + - name: ApoptoticCellProperty + + - name: CellPropertyCollection + excluded_methods: + - GetCellPropertyRegistry + + - name: CellData + - name: CellLabel + - name: CellAncestor + - name: CellId + - name: CellEdgeData + + - name: CellPropertyRegistry + excluded_methods: + - rGetAllCellProperties + - TakeOwnership + - Get + + - name: CellVecData + excluded: True + + ##=== cell_based: Chaste/cell_based/src/common + - name: SimulationTime + + - name: OdeLinearSystemSolver + excluded: True + + ##=== cell_based: Chaste/cell_based/src/population + - name: AbstractCellPopulation + excluded_methods: + - rGetCells + source_includes: + - AbstractCellBasedSimulation.hpp + - PybindUblasTypeCaster.hpp + + - name: AbstractOffLatticeCellPopulation + source_includes: + - PybindUblasTypeCaster.hpp + + - name: AbstractCentreBasedCellPopulation + template_substitutions: + - signature: + replacement: [[2, 2], [3, 3]] + excluded_methods: + - MarkSpring + - UnmarkSpring + source_includes: + - PybindUblasTypeCaster.hpp + + - name: AbstractOnLatticeCellPopulation + excluded_methods: + - GetNodeCorrespondingToCell + - MarkSpring + - UnmarkSpring + + - name: CaBasedCellPopulation + excluded_methods: + - rGetMesh + - GetTetrahedralMeshForPdeModifier + - GetNodeCorrespondingToCell + - rGetAvailableSpaces + custom_generator: &population_template "CPPWG_SOURCEROOT/pychaste/dynamic/templates/PopulationWriterCustomTemplate.py" + source_includes: &population_includes + - AbstractCellBasedSimulation.hpp + - AbstractImmersedBoundaryDivisionRule.hpp + - AbstractVertexBasedDivisionRule.hpp + - BoundaryNodeWriter.hpp + - CellAgesWriter.hpp + - CellAncestorWriter.hpp + - CellAppliedForceWriter.hpp + - CellCycleModelProteinConcentrationsWriter.hpp + - CellDataItemWriter.hpp + - CellDeltaNotchWriter.hpp + - CellDivisionLocationsWriter.hpp + - CellIdWriter.hpp + - CellLabelWriter.hpp + - CellLocationIndexWriter.hpp + - CellMutationStatesCountWriter.hpp + - CellMutationStatesWriter.hpp + - CellPopulationAdjacencyMatrixWriter.hpp + - CellPopulationAreaWriter.hpp + - CellPopulationElementWriter.hpp + - CellProliferativePhasesCountWriter.hpp + - CellProliferativePhasesWriter.hpp + - CellProliferativeTypesCountWriter.hpp + - CellProliferativeTypesWriter.hpp + - CellRadiusWriter.hpp + - CellRemovalLocationsWriter.hpp + - CellRosetteRankWriter.hpp + - CellVolumesWriter.hpp + - HeterotypicBoundaryLengthWriter.hpp + - LegacyCellProliferativeTypesWriter.hpp + - NodeLocationWriter.hpp + - NodeVelocityWriter.hpp + - PottsMeshWriter.hpp + - PybindUblasTypeCaster.hpp + - RadialCellDataDistributionWriter.hpp + - VertexIntersectionSwapLocationsWriter.hpp + - VertexT1SwapLocationsWriter.hpp + - VertexT2SwapLocationsWriter.hpp + - VertexT3SwapLocationsWriter.hpp + - VoronoiDataWriter.hpp + + - name: ImmersedBoundaryCellPopulation + excluded_methods: + - rGetMesh + - GetTetrahedralMeshForPdeModifier + custom_generator: *population_template + source_includes: *population_includes + + - name: MeshBasedCellPopulation + excluded_methods: + - GetVoronoiTessellation + - rGetNodePairs + - GetTetrahedralMeshForPdeModifier + - rGetMesh + custom_generator: *population_template + source_includes: *population_includes + + - name: MeshBasedCellPopulationWithGhostNodes + custom_generator: *population_template + source_includes: *population_includes + + - name: NodeBasedCellPopulation + excluded_methods: + - rGetNodePairs + - GetTetrahedralMeshForPdeModifier + - rGetMesh + custom_generator: *population_template + source_includes: *population_includes + + - name: NodeBasedCellPopulationWithBuskeUpdate + excluded_methods: + - rGetMesh + custom_generator: *population_template + source_includes: *population_includes + + - name: NodeBasedCellPopulationWithParticles + excluded_methods: + - rGetMesh + custom_generator: *population_template + source_includes: *population_includes + + - name: VertexBasedCellPopulation + excluded_methods: + - GetElementCorrespondingToCell + - GetElement + - GetTetrahedralMeshForPdeModifier + - rGetMesh + custom_generator: *population_template + source_includes: *population_includes + + - name: PottsBasedCellPopulation + excluded_methods: + - GetElementCorrespondingToCell + - GetElement + - GetTetrahedralMeshForPdeModifier + - rGetMesh + - GetElementTessellation + - GetMutableMesh + custom_generator: *population_template + source_includes: *population_includes + + - name: CellwiseDataGradient + excluded: True + + ##=== cell_based: Chaste/cell_based/src/population/boundary_conditions + - name: AbstractCellPopulationBoundaryCondition + source_includes: + - PybindUblasTypeCaster.hpp + + - name: PlaneBoundaryCondition + source_includes: + - PybindUblasTypeCaster.hpp + + - name: SlidingBoundaryCondition + source_includes: + - PybindUblasTypeCaster.hpp + + - name: SphereGeometryBoundaryCondition + source_includes: + - PybindUblasTypeCaster.hpp + + ##=== cell_based: Chaste/cell_based/src/population/division_rules + - name: AbstractCentreBasedDivisionRule + source_includes: + - PybindUblasTypeCaster.hpp + + - name: AbstractCaBasedDivisionRule + + - name: AbstractImmersedBoundaryDivisionRule + source_includes: + - PybindUblasTypeCaster.hpp + + - name: AbstractVertexBasedDivisionRule + source_includes: + - PybindUblasTypeCaster.hpp + + - name: ExclusionCaBasedDivisionRule + + - name: FixedCentreBasedDivisionRule + source_includes: + - PybindUblasTypeCaster.hpp + + - name: FixedVertexBasedDivisionRule + source_includes: + - PybindUblasTypeCaster.hpp + + - name: RandomDirectionCentreBasedDivisionRule + source_includes: + - PybindUblasTypeCaster.hpp + + - name: RandomDirectionVertexBasedDivisionRule + source_includes: + - PybindUblasTypeCaster.hpp + + - name: ShortAxisImmersedBoundaryDivisionRule + source_includes: + - PybindUblasTypeCaster.hpp + + - name: ShortAxisVertexBasedDivisionRule + source_includes: + - PybindUblasTypeCaster.hpp + + - name: ShovingCaBasedDivisionRule + + - name: VonMisesVertexBasedDivisionRule + source_includes: + - PybindUblasTypeCaster.hpp + + ##=== cell_based: Chaste/cell_based/src/population/forces + - name: AbstractForce + - name: AbstractTwoBodyInteractionForce + source_includes: + - PybindUblasTypeCaster.hpp + + - name: AbstractImmersedBoundaryForce + + - name: BuskeAdhesiveForce + source_includes: + - PybindUblasTypeCaster.hpp + + - name: BuskeCompressionForce + + - name: BuskeElasticForce + source_includes: + - PybindUblasTypeCaster.hpp + + - name: ChemotacticForce + - name: DiffusionForce + - name: FarhadifarForce + + - name: GeneralisedLinearSpringForce + source_includes: + - PybindUblasTypeCaster.hpp + + - name: ImmersedBoundaryKinematicFeedbackForce + - name: ImmersedBoundaryLinearDifferentialAdhesionForce + - name: ImmersedBoundaryLinearInteractionForce + - name: ImmersedBoundaryLinearMembraneForce + - name: ImmersedBoundaryMorseInteractionForce + - name: ImmersedBoundaryMorseMembraneForce + + - name: NagaiHondaForce + - name: RepulsionForce + + - name: WelikyOsterForce + excluded_methods: + - GetLineTensionParameter + + - name: DifferentialAdhesionGeneralisedLinearSpringForce + + - name: NagaiHondaDifferentialAdhesionForce + excluded_methods: + - GetAdhesionParameter + + - name: PlanarPolarisedFarhadifarForce + excluded_methods: + - GetLineTensionParameter + + - name: DiscreteSystemForceCalculator + excluded: True + + ##=== cell_based: Chaste/cell_based/src/population/killers + - name: AbstractCellKiller + + - name: ApoptoticCellKiller + - name: IsolatedLabelledCellKiller + + - name: PlaneBasedCellKiller + source_includes: + - PybindUblasTypeCaster.hpp + + - name: RandomCellKiller + - name: T2SwapCellKiller + - name: TargetedCellKiller + + ##=== cell_based: Chaste/cell_based/src/population/srn_update + - name: VertexBasedPopulationSrn + + ##=== cell_based: Chaste/cell_based/src/population/update_rules + - name: AbstractUpdateRule + - name: AbstractCaUpdateRule + - name: AbstractPottsUpdateRule + - name: AbstractCaSwitchingUpdateRule + + - name: AdhesionPottsUpdateRule + - name: ChemotaxisPottsUpdateRule + - name: DifferentialAdhesionPottsUpdateRule + - name: DiffusionCaUpdateRule + - name: RandomCaSwitchingUpdateRule + - name: SurfaceAreaConstraintPottsUpdateRule + - name: VolumeConstraintPottsUpdateRule + + ##=== cell_based: Chaste/cell_based/src/properties/mutations + - name: AbstractCellMutationState + - name: ApcOneHitCellMutationState + - name: ApcTwoHitCellMutationState + - name: BetaCateninOneHitCellMutationState + - name: WildTypeCellMutationState + + ##=== cell_based: Chaste/cell_based/src/properties/proliferative_types + - name: AbstractCellProliferativeType + - name: StemCellProliferativeType + - name: DefaultCellProliferativeType + - name: TransitCellProliferativeType + - name: DifferentiatedCellProliferativeType + + ##=== cell_based: Chaste/cell_based/src/simulation + - name: AbstractCellBasedSimulation + template_substitutions: + - signature: + replacement: [[2, 2], [3, 3]] + excluded_methods: + - GetSimulationModifiers + + - name: OffLatticeSimulation + - name: OnLatticeSimulation + + - name: CellBasedSimulationArchiver + excluded: True + + ##=== cell_based: Chaste/cell_based/src/simulation/modifiers + - name: AbstractCellBasedSimulationModifier + - name: AbstractTargetAreaModifier + + - name: DeltaNotchTrackingModifier + - name: DeltaNotchEdgeTrackingModifier + - name: DeltaNotchEdgeInteriorTrackingModifier + + - name: DivisionBiasTrackingModifier + source_includes: + - PybindUblasTypeCaster.hpp + + - name: ExtrinsicPullModifier + - name: ImmersedBoundarySimulationModifier + + - name: ImmersedBoundarySvgWriter + source_includes: + - PybindUblasTypeCaster.hpp + + - name: NormallyDistributedTargetAreaModifier + - name: SimpleTargetAreaModifier + - name: TargetAreaLinearGrowthModifier + - name: VolumeTrackingModifier + + ##=== cell_based: Chaste/cell_based/src/simulation/numerical_methods + - name: AbstractNumericalMethod + - name: ForwardEulerNumericalMethod + + - name: StepSizeException + excluded: True + + ##=== cell_based: Chaste/cell_based/src/srn + - name: AbstractSrnModel + - name: AbstractOdeSrnModel + - name: NullSrnModel + - name: CellSrnModel + - name: DeltaNotchSrnModel + - name: DeltaNotchEdgeSrnModel + - name: DeltaNotchInteriorSrnModel + - name: Goldbeter1991SrnModel + + ##=== cell_based: Chaste/cell_based/src/writers + - name: AbstractCellBasedWriter + + ##=== cell_based: Chaste/cell_based/src/writers/cell_writers + - name: AbstractCellWriter + source_includes: + - AbstractCellPopulation.hpp + - PybindUblasTypeCaster.hpp + + - name: CellAgesWriter + source_includes: + - AbstractCellPopulation.hpp + + - name: CellAncestorWriter + source_includes: + - AbstractCellPopulation.hpp + + - name: CellAppliedForceWriter + source_includes: + - AbstractCellPopulation.hpp + - PybindUblasTypeCaster.hpp + + - name: CellCycleModelProteinConcentrationsWriter + source_includes: + - AbstractCellPopulation.hpp + + - name: CellDataItemWriter + source_includes: + - AbstractCellPopulation.hpp + + - name: CellDeltaNotchWriter + source_includes: + - AbstractCellPopulation.hpp + + - name: CellIdWriter + source_includes: + - AbstractCellPopulation.hpp + + - name: CellLabelWriter + source_includes: + - AbstractCellPopulation.hpp + + - name: CellLocationIndexWriter + source_includes: + - AbstractCellPopulation.hpp + + - name: CellMutationStatesWriter + source_includes: + - AbstractCellPopulation.hpp + + - name: CellProliferativePhasesWriter + source_includes: + - AbstractCellPopulation.hpp + + - name: CellProliferativeTypesWriter + source_includes: + - AbstractCellPopulation.hpp + + - name: CellRadiusWriter + source_includes: + - AbstractCellPopulation.hpp + + - name: CellRosetteRankWriter + source_includes: + - AbstractCellPopulation.hpp + + - name: CellVolumesWriter + source_includes: + - AbstractCellPopulation.hpp + + - name: ImmersedBoundaryBoundaryCellWriter + source_includes: + - AbstractCellPopulation.hpp + - ImmersedBoundaryCellPopulation.hpp + + - name: ImmersedBoundaryNeighbourNumberWriter + source_includes: + - AbstractCellPopulation.hpp + - ImmersedBoundaryCellPopulation.hpp + + - name: LegacyCellProliferativeTypesWriter + source_includes: + - AbstractCellPopulation.hpp + + ##=== cell_based: Chaste/cell_based/src/writers/population_writers + - name: AbstractCellPopulationWriter + source_includes: &population_writer_includes + - AbstractCellPopulation.hpp + - MeshBasedCellPopulation.hpp + - PottsBasedCellPopulation.hpp + - CaBasedCellPopulation.hpp + - ImmersedBoundaryCellPopulation.hpp + - NodeBasedCellPopulation.hpp + - VertexBasedCellPopulation.hpp + + - name: BoundaryNodeWriter + source_includes: *population_writer_includes + + - name: CellPopulationAdjacencyMatrixWriter + source_includes: *population_writer_includes + + - name: CellPopulationAreaWriter + source_includes: *population_writer_includes + + - name: CellPopulationElementWriter + source_includes: *population_writer_includes + + - name: HeterotypicBoundaryLengthWriter + source_includes: *population_writer_includes + + - name: NodeLocationWriter + source_includes: *population_writer_includes + + - name: NodeVelocityWriter + source_includes: *population_writer_includes + + - name: RadialCellDataDistributionWriter + source_includes: *population_writer_includes + + - name: VertexIntersectionSwapLocationsWriter + source_includes: *population_writer_includes + + - name: VertexT1SwapLocationsWriter + source_includes: *population_writer_includes + + - name: VertexT2SwapLocationsWriter + source_includes: *population_writer_includes + + - name: VertexT3SwapLocationsWriter + source_includes: *population_writer_includes + + - name: VoronoiDataWriter + source_includes: *population_writer_includes + + ##=== cell_based: Chaste/cell_based/src/writers/population_count_writers + - name: AbstractCellPopulationCountWriter + source_includes: *population_writer_includes + + - name: CellMutationStatesCountWriter + source_includes: *population_writer_includes + + - name: CellProliferativePhasesCountWriter + source_includes: *population_writer_includes + + - name: CellProliferativeTypesCountWriter + source_includes: *population_writer_includes + + ##=== cell_based: Chaste/cell_based/src/writers/population_event_writers + - name: AbstractCellPopulationEventWriter + source_includes: *population_writer_includes + + - name: CellDivisionLocationsWriter + source_includes: *population_writer_includes + + - name: CellRemovalLocationsWriter + source_includes: *population_writer_includes + + ##=== cell_based: Chaste/pychaste/src/cpp/cell_based + - name: AttractingPlaneBoundaryCondition + source_includes: + - PybindUblasTypeCaster.hpp + + - name: VtkSceneModifier + - name: PythonSimulationModifier + + ##=== visualization: Chaste/pychaste/src/cpp/visualization + - name: VtkScene + source_includes: + - PybindVTKTypeCaster.hpp + + - name: AbstractPyChasteActorGenerator + source_includes: + - PybindUblasTypeCaster.hpp + + - name: CellPopulationPyChasteActorGenerator + +prefix_text: | + /* + + Copyright (c) 2005-2024, University of Oxford. + All rights reserved. + + University of Oxford means the Chancellor, Masters and Scholars of the + University of Oxford, having an administrative office at Wellington + Square, Oxford OX1 2JD, UK. + + This file is part of Chaste. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + */ + + // This file is auto-generated; manual changes will be overwritten. + // To make enduring changes, see pychaste/dynamic/config.yaml. diff --git a/pychaste/dynamic/templates/CellsGeneratorCustomTemplate.py b/pychaste/dynamic/templates/CellsGeneratorCustomTemplate.py new file mode 100644 index 0000000000..6e9ae2f069 --- /dev/null +++ b/pychaste/dynamic/templates/CellsGeneratorCustomTemplate.py @@ -0,0 +1,89 @@ +"""Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import cppwg.templates.custom + + +class CellsGeneratorCustomTemplate(cppwg.templates.custom.Custom): + + def __init__(self): + pass + + def get_class_cpp_pre_code(self, class_name): + """ + Creates custom pybind11 trampoline wrapper code for CellsGenerator. + """ + + code = f""" +class {class_name}_Overrides : {class_name}{{ + public: + using {class_name}::{class_name}; + std::vector GenerateBasic(unsigned numCells, const std::vector locationIndices=std::vector(), boost::shared_ptr pCellProliferativeType=boost::shared_ptr()) + {{ + std::vector cells; + {class_name}::GenerateBasic(cells, numCells, locationIndices, pCellProliferativeType); + return cells; + }}; + std::vector GenerateBasicRandom(unsigned numCells, boost::shared_ptr pCellProliferativeType=boost::shared_ptr()) + {{ + std::vector cells; + {class_name}::GenerateBasicRandom(cells, numCells, pCellProliferativeType); + return cells; + }}; + std::vector GenerateGivenLocationIndices(const std::vector locationIndices, boost::shared_ptr pCellProliferativeType=boost::shared_ptr()) + {{ + std::vector cells; + {class_name}::GenerateGivenLocationIndices(cells, locationIndices, pCellProliferativeType); + return cells; + }}; +}}; + +""" + return code + + def get_class_cpp_def_code(self, class_name): + """ + Creates custom pybind11 class wrapper code for CellsGenerator. + """ + + code = f""" + .def("GenerateBasic", + (std::vector({class_name}::*)(unsigned int, const std::vector, boost::shared_ptr)) + &{class_name}_Overrides::GenerateBasic, " " , py::arg("numCells"), py::arg("locationIndices") = std::vector(), py::arg("pCellProliferativeType") = boost::shared_ptr()) + .def("GenerateBasicRandom", + (std::vector({class_name}::*)(unsigned int, boost::shared_ptr)) + &{class_name}_Overrides::GenerateBasicRandom, " " , py::arg("numCells"), py::arg("pCellProliferativeType") = boost::shared_ptr()) + .def("GenerateGivenLocationIndices", + (std::vector({class_name}::*)(const std::vector locationIndices, boost::shared_ptr)) + &{class_name}_Overrides::GenerateGivenLocationIndices, " " , py::arg("locationIndices"), py::arg("pCellProliferativeType") = boost::shared_ptr()) +""" + return code diff --git a/pychaste/dynamic/templates/MutableElementCustomTemplate.py b/pychaste/dynamic/templates/MutableElementCustomTemplate.py new file mode 100644 index 0000000000..df60de5dc6 --- /dev/null +++ b/pychaste/dynamic/templates/MutableElementCustomTemplate.py @@ -0,0 +1,54 @@ +"""Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import cppwg.templates.custom + + +class MutableElementCustomTemplate(cppwg.templates.custom.Custom): + + def __init__(self): + pass + + def get_class_cpp_def_code(self, class_name): + """ + Adds MutableElement constructor not implemented in 1D template. + """ + + _, ELEMENT_DIM, SPACE_DIM = class_name.split("_") + + if ELEMENT_DIM == "1": + return "" + + code = f"""\ + .def(py::init*> const &>(), py::arg("index"), py::arg("rNodes")) +""" + return code diff --git a/pychaste/dynamic/templates/PopulationWriterCustomTemplate.py b/pychaste/dynamic/templates/PopulationWriterCustomTemplate.py new file mode 100644 index 0000000000..4b1a38d1f3 --- /dev/null +++ b/pychaste/dynamic/templates/PopulationWriterCustomTemplate.py @@ -0,0 +1,67 @@ +"""Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import cppwg.templates.custom + + +class PopulationWriterCustomTemplate(cppwg.templates.custom.Custom): + + def __init__(self): + pass + + def get_class_cpp_def_code(self, class_name): + """ + Creates custom wrapper code for adding population and cell writers. + """ + population_writers = ["VoronoiDataWriter"] + + cell_writers = ["CellLabelWriter"] + + population_writer_template = """\ + .def("AddPopulationWriter{writer}", &{class_name}::AddPopulationWriter<{writer}>) +""" + + cell_writer_template = """\ + .def("AddCellWriter{writer}", &{class_name}::AddCellWriter<{writer}>) +""" + + code = "" + + for writer in population_writers: + replacements = {"class_name": class_name, "writer": writer} + code += population_writer_template.format(**replacements) + + for writer in cell_writers: + replacements = {"class_name": class_name, "writer": writer} + code += cell_writer_template.format(**replacements) + + return code diff --git a/pychaste/dynamic/templates/ReplicatableVectorCustomTemplate.py b/pychaste/dynamic/templates/ReplicatableVectorCustomTemplate.py new file mode 100644 index 0000000000..7d37cddfd5 --- /dev/null +++ b/pychaste/dynamic/templates/ReplicatableVectorCustomTemplate.py @@ -0,0 +1,50 @@ +"""Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import cppwg.templates.custom + + +class ReplicatableVectorCustomTemplate(cppwg.templates.custom.Custom): + + def __init__(self): + pass + + def get_class_cpp_def_code(self, class_name): + """ + Adds custom wrapper code for overloaded operator[]. + """ + + code = f"""\ + .def("__getitem__", &{class_name}::operator[]) + .def("__setitem__", &{class_name}::operator[])\n""" + + return code diff --git a/pychaste/dynamic/typecasters/3rdparty/dolfinx/COPYING b/pychaste/dynamic/typecasters/3rdparty/dolfinx/COPYING new file mode 100644 index 0000000000..85f73fd70a --- /dev/null +++ b/pychaste/dynamic/typecasters/3rdparty/dolfinx/COPYING @@ -0,0 +1,680 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. + + +Note: Individual files contain the following tag instead of the full license text. + +SPDX-License-Identifier: LGPL-3.0-or-later +This enables machine processing of license information based on the SPDX License Identifiers that are here available: http://spdx.org/licenses/ \ No newline at end of file diff --git a/pychaste/dynamic/typecasters/3rdparty/dolfinx/COPYING.LESSER b/pychaste/dynamic/typecasters/3rdparty/dolfinx/COPYING.LESSER new file mode 100644 index 0000000000..491226c85a --- /dev/null +++ b/pychaste/dynamic/typecasters/3rdparty/dolfinx/COPYING.LESSER @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General 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. diff --git a/pychaste/dynamic/typecasters/3rdparty/dolfinx/PybindPetscTypeCaster.hpp b/pychaste/dynamic/typecasters/3rdparty/dolfinx/PybindPetscTypeCaster.hpp new file mode 100644 index 0000000000..9e4badeec1 --- /dev/null +++ b/pychaste/dynamic/typecasters/3rdparty/dolfinx/PybindPetscTypeCaster.hpp @@ -0,0 +1,87 @@ +// Copyright (C) 2017-2023 Chris Richardson and Garth N. Wells +// +// This file is part of DOLFINx (https://www.fenicsproject.org) +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +// Modifications: +// This file, originally named "caster_petsc.h", has been modified +// to use pybind11 instead of nanobind. + +#ifndef PYBINDPETSCTYPECASTER_HPP_ +#define PYBINDPETSCTYPECASTER_HPP_ + +#include +#include + +#include +#include +#include + +// pybind11 casters for PETSc/petsc4py objects + +// Import petsc4py on demand +#define VERIFY_PETSC4PY_FROMPY(func) \ + if (!func) \ + { \ + if (import_petsc4py() != 0) \ + return false; \ + } + +#define VERIFY_PETSC4PY_FROMCPP(func) \ + if (!func) \ + { \ + if (import_petsc4py() != 0) \ + return {}; \ + } + +// Macro for casting between PETSc and petsc4py objects +#define PETSC_CASTER_MACRO(TYPE, P4PYTYPE, NAME) \ + template <> \ + class type_caster<_p_##TYPE> \ + { \ + public: \ + PYBIND11_TYPE_CASTER(TYPE, const_name(#NAME)); \ + bool load(handle src, bool) \ + { \ + VERIFY_PETSC4PY_FROMPY(PyPetsc##P4PYTYPE##_Get); \ + if (PyObject_TypeCheck(src.ptr(), &PyPetsc##P4PYTYPE##_Type) != 0) \ + { \ + value = PyPetsc##P4PYTYPE##_Get(src.ptr()); \ + return true; \ + } \ + else \ + return false; \ + } \ + \ + static handle cast(TYPE src, return_value_policy policy, handle) \ + { \ + VERIFY_PETSC4PY_FROMCPP(PyPetsc##P4PYTYPE##_New); \ + if (policy == return_value_policy::take_ownership) \ + { \ + PyObject *obj = PyPetsc##P4PYTYPE##_New(src); \ + PetscObjectDereference((PetscObject)src); \ + return pybind11::handle(obj); \ + } \ + else if (policy == return_value_policy::automatic_reference or \ + policy == return_value_policy::reference) \ + { \ + PyObject *obj = PyPetsc##P4PYTYPE##_New(src); \ + return pybind11::handle(obj); \ + } \ + else \ + { \ + return {}; \ + } \ + } \ + \ + operator TYPE() { return value; } \ + } + +namespace pybind11::detail +{ + PETSC_CASTER_MACRO(Mat, Mat, mat); + PETSC_CASTER_MACRO(Vec, Vec, vec); +} // namespace pybind11::detail + +#endif // PYBINDPETSCTYPECASTER_HPP_ diff --git a/pychaste/dynamic/typecasters/3rdparty/smtk/LICENSE.txt b/pychaste/dynamic/typecasters/3rdparty/smtk/LICENSE.txt new file mode 100644 index 0000000000..0f0d7b888a --- /dev/null +++ b/pychaste/dynamic/typecasters/3rdparty/smtk/LICENSE.txt @@ -0,0 +1,38 @@ +SMTK License Version 1.0 +======================================================================== +Copyright (c) 2012 Kitware Inc. 28 Corporate Drive +Clifton Park, NY, 12065, USA. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of Kitware nor the names of any contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The following files and directories come from third parties. Check the +contents of these for details on the specifics of their respective +licenses. +- - - - - - - - - - - - - - - - - - - - - - - - +Thirdparty/pugixml +Thirdparty/PyYaml +Thidpary/rtvl +CMake/FindDocutils.cmake diff --git a/pychaste/dynamic/typecasters/3rdparty/smtk/PybindVTKTypeCaster.hpp b/pychaste/dynamic/typecasters/3rdparty/smtk/PybindVTKTypeCaster.hpp new file mode 100644 index 0000000000..652477773f --- /dev/null +++ b/pychaste/dynamic/typecasters/3rdparty/smtk/PybindVTKTypeCaster.hpp @@ -0,0 +1,139 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= + +#ifndef PYBINDVTKTYPECASTER_HPP_ +#define PYBINDVTKTYPECASTER_HPP_ + +#include +#include + +#include + +#include "vtkNew.h" +#include "vtkObjectBase.h" +#include "vtkPythonUtil.h" +#include "vtkSmartPointer.h" + +namespace pybind11 +{ +namespace detail +{ + +/// Thanks to Eric Cosineau, who figured out a more general means of casting +/// to/from VTK's python wrappings: +/// https://github.com/EricCousineau-TRI/repro/blob/b9e02d6d5a71f6315b80759ba1628b4bb383c0b8/python/vtk_pybind/vtk_pybind.h + +/// Direct access to VTK class. +template +struct type_caster::value> > +{ +private: + Class* value; + +public: + static constexpr auto name = _(); + + static handle cast(const Class* src, return_value_policy policy, handle /*parent*/) + { + if (!src) + return none().release(); + if (policy == return_value_policy::take_ownership) + { + throw cast_error("vtk_pybind: `take_ownership` does not make sense in VTK?"); + } + if (policy == return_value_policy::copy) + { + throw cast_error("vtk_pybind: `copy` does not make sense in VTK?"); + } + return vtkPythonUtil::GetObjectFromPointer(const_cast(src)); + } + + static handle cast(const Class& src, return_value_policy policy, handle parent) + { + return cast(&src, policy, parent); + } + + operator Class*() { return value; } + operator Class&() { return *value; } + // Does this even make sense in VTK? + operator Class &&() && { return std::move(*value); } + + template + using cast_op_type = pybind11::detail::movable_cast_op_type; + + bool load(handle src, bool /* convert */) + { + value = dynamic_cast( + vtkPythonUtil::GetPointerFromObject(src.ptr(), type_id().c_str())); + return value != nullptr; + } +}; + +/// VTK Pointer-like object - may be non-copyable. +template +struct vtk_ptr_cast_only +{ +protected: + using Class = intrinsic_t())>; + using value_caster_type = type_caster; + +public: + static constexpr auto name = _(); + static handle cast(const Ptr& ptr, return_value_policy policy, handle parent) + { + return value_caster_type::cast(*ptr, policy, parent); + ; + } +}; + +/// VTK Pointer-like object - copyable / movable. +template +struct vtk_ptr_cast_and_load : public vtk_ptr_cast_only +{ +private: + Ptr value; + // N.B. Can't easily access base versions... + using Class = intrinsic_t())>; + using value_caster_type = type_caster; + +public: + operator Ptr&() { return value; } + // Does this even make sense in VTK? + operator Ptr &&() && { return std::move(value); } + + template + using cast_op_type = pybind11::detail::movable_cast_op_type; + + bool load(handle src, bool convert) + { + value_caster_type value_caster; + if (!value_caster.load(src, convert)) + { + return false; + } + value = Ptr(value_caster.operator Class*()); + return true; + } +}; + +template +struct type_caster > : public vtk_ptr_cast_and_load > +{ +}; + +template +struct type_caster > : public vtk_ptr_cast_only > +{ +}; + +} // namespace detail +} // namespace pybind11 + +#endif // PYBINDVTKTYPECASTER_HPP_ diff --git a/pychaste/dynamic/typecasters/PybindUblasTypeCaster.hpp b/pychaste/dynamic/typecasters/PybindUblasTypeCaster.hpp new file mode 100644 index 0000000000..8fd155dbfd --- /dev/null +++ b/pychaste/dynamic/typecasters/PybindUblasTypeCaster.hpp @@ -0,0 +1,98 @@ +/* + +Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef PYBINDUBLASTYPECASTER_HPP_ +#define PYBINDUBLASTYPECASTER_HPP_ + +#include "UblasIncludes.hpp" +#include "UblasVectorInclude.hpp" + +#include +#include +#include + +#define PYBIND11_CVECTOR_TYPECASTER(T, N) \ + namespace pybind11 \ + { \ + namespace detail \ + { \ + template <> \ + struct type_caster > \ + { \ + public: \ + typedef boost::numeric::ublas::c_vector c_vector_##T##_##N##_t; \ + PYBIND11_TYPE_CASTER(c_vector_##T##_##N##_t, const_name("c_vector_" #T "_" #N "_t")); \ + bool load(handle src, bool convert) \ + { \ + if (!convert && !array_t::check_(src)) \ + { \ + return false; \ + } \ + auto buf = array_t::ensure(src); \ + if (!buf) \ + { \ + return false; \ + } \ + if (buf.ndim() != 1 or buf.shape()[0] != N) \ + { \ + return false; \ + } \ + value.resize(N); \ + for (unsigned i = 0; i < N; ++i) \ + { \ + value[i] = buf.data()[i]; \ + } \ + return true; \ + } \ + static handle cast(const boost::numeric::ublas::c_vector& src, \ + return_value_policy, \ + handle) \ + { \ + std::vector shape(1, N); \ + std::vector strides(1, sizeof(T)); \ + T* data = src.size() ? const_cast(&src[0]) : static_cast(NULL); \ + array a(std::move(shape), std::move(strides), data); \ + return a.release(); \ + } \ + }; \ + } \ + } + +PYBIND11_CVECTOR_TYPECASTER(double, 2); +PYBIND11_CVECTOR_TYPECASTER(double, 3); + +#undef PYBIND11_CVECTOR_TYPECASTER + +#endif // PYBINDUBLASTYPECASTER_HPP_ diff --git a/pychaste/src/cpp/cell_based/AttractingPlaneBoundaryCondition.cpp b/pychaste/src/cpp/cell_based/AttractingPlaneBoundaryCondition.cpp new file mode 100644 index 0000000000..ee484f64c1 --- /dev/null +++ b/pychaste/src/cpp/cell_based/AttractingPlaneBoundaryCondition.cpp @@ -0,0 +1,234 @@ +/* + +Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "AttractingPlaneBoundaryCondition.hpp" +#include "AbstractCentreBasedCellPopulation.hpp" +#include "UblasIncludes.hpp" +#include "VertexBasedCellPopulation.hpp" + +template +AttractingPlaneBoundaryCondition::AttractingPlaneBoundaryCondition( + AbstractCellPopulation* pCellPopulation, + c_vector point, + c_vector normal) + : AbstractCellPopulationBoundaryCondition(pCellPopulation), + mPointOnPlane(normal), + mUseJiggledNodesOnPlane(false), + mAttractionThreshold(0.1) +{ + assert(norm_2(normal) > 0.0); + mNormalToPlane = normal / norm_2(normal); +} + +template +const c_vector& AttractingPlaneBoundaryCondition::rGetPointOnPlane() const +{ + return mPointOnPlane; +} + +template +const c_vector& AttractingPlaneBoundaryCondition::rGetNormalToPlane() const +{ + return mNormalToPlane; +} + +template +void AttractingPlaneBoundaryCondition::SetUseJiggledNodesOnPlane(bool useJiggledNodesOnPlane) +{ + mUseJiggledNodesOnPlane = useJiggledNodesOnPlane; +} + +template +bool AttractingPlaneBoundaryCondition::GetUseJiggledNodesOnPlane() +{ + return mUseJiggledNodesOnPlane; +} + +template +void AttractingPlaneBoundaryCondition::ImposeBoundaryCondition(const std::map*, c_vector >& rOldLocations) +{ + ///\todo Move this to constructor. If this is in the constructor then Exception always throws. + if (dynamic_cast*>(this->mpCellPopulation) == NULL) + { + EXCEPTION("AttractingPlaneBoundaryCondition requires a subclass of AbstractOffLatticeCellPopulation."); + } + + assert((dynamic_cast*>(this->mpCellPopulation)) + || (SPACE_DIM == ELEMENT_DIM && (dynamic_cast*>(this->mpCellPopulation)))); + + // This is a magic number + double max_jiggle = 1e-4; + + if (SPACE_DIM != 1) + { + if (dynamic_cast*>(this->mpCellPopulation)) + { + for (typename AbstractCellPopulation::Iterator cell_iter = this->mpCellPopulation->Begin(); + cell_iter != this->mpCellPopulation->End(); + ++cell_iter) + { + unsigned node_index = this->mpCellPopulation->GetLocationIndexUsingCell(*cell_iter); + Node* p_node = this->mpCellPopulation->GetNode(node_index); + + c_vector node_location = p_node->rGetLocation(); + + double signed_distance = inner_prod(node_location - mPointOnPlane, mNormalToPlane); + if (signed_distance > 0.0 or std::abs(signed_distance) < mAttractionThreshold) + { + // For the closest point on the plane we travel from node_location the signed_distance in the direction of -mNormalToPlane + c_vector nearest_point; + if (mUseJiggledNodesOnPlane) + { + nearest_point = node_location - (signed_distance + max_jiggle * RandomNumberGenerator::Instance()->ranf()) * mNormalToPlane; + } + else + { + nearest_point = node_location - signed_distance * mNormalToPlane; + } + p_node->rGetModifiableLocation() = nearest_point; + } + } + } + else + { + assert(SPACE_DIM == ELEMENT_DIM); // LCOV_EXCL_LINE + assert(dynamic_cast*>(this->mpCellPopulation)); + + // Iterate over all nodes and update their positions according to the boundary conditions + unsigned num_nodes = this->mpCellPopulation->GetNumNodes(); + for (unsigned node_index = 0; node_index < num_nodes; node_index++) + { + Node* p_node = this->mpCellPopulation->GetNode(node_index); + c_vector node_location = p_node->rGetLocation(); + + double signed_distance = inner_prod(node_location - mPointOnPlane, mNormalToPlane); + if (signed_distance > 0.0 or std::abs(signed_distance) < mAttractionThreshold) + { + // For the closest point on the plane we travel from node_location the signed_distance in the direction of -mNormalToPlane + c_vector nearest_point; + if (mUseJiggledNodesOnPlane) + { + nearest_point = node_location - (signed_distance + max_jiggle * RandomNumberGenerator::Instance()->ranf()) * mNormalToPlane; + } + else + { + nearest_point = node_location - signed_distance * mNormalToPlane; + } + p_node->rGetModifiableLocation() = nearest_point; + } + } + } + } + else + { + // DIM == 1 + NEVER_REACHED; + // AttractingPlaneBoundaryCondition::ImposeBoundaryCondition is not implemented in 1D + } +} + +template +bool AttractingPlaneBoundaryCondition::VerifyBoundaryCondition() +{ + bool condition_satisfied = true; + + if (SPACE_DIM == 1) + { + EXCEPTION("AttractingPlaneBoundaryCondition is not implemented in 1D"); + } + else + { + for (typename AbstractCellPopulation::Iterator cell_iter = this->mpCellPopulation->Begin(); + cell_iter != this->mpCellPopulation->End(); + ++cell_iter) + { + c_vector cell_location = this->mpCellPopulation->GetLocationOfCellCentre(*cell_iter); + + if (inner_prod(cell_location - mPointOnPlane, mNormalToPlane) > 0.0) + { + condition_satisfied = false; + break; + } + } + } + + return condition_satisfied; +} + +template +void AttractingPlaneBoundaryCondition::SetPointOnPlane(const c_vector& rPoint) +{ + mPointOnPlane = rPoint; +} + +template +void AttractingPlaneBoundaryCondition::SetAttractionThreshold(double attractionThreshold) +{ + mAttractionThreshold = attractionThreshold; +} + +template +void AttractingPlaneBoundaryCondition::OutputCellPopulationBoundaryConditionParameters(out_stream& rParamsFile) +{ + *rParamsFile << "\t\t\t"; + for (unsigned index = 0; index != SPACE_DIM - 1U; index++) // Note: inequality avoids testing index < 0U when DIM=1 + { + *rParamsFile << mPointOnPlane[index] << ","; + } + *rParamsFile << mPointOnPlane[SPACE_DIM - 1] << "\n"; + + *rParamsFile << "\t\t\t"; + for (unsigned index = 0; index != SPACE_DIM - 1U; index++) // Note: inequality avoids testing index < 0U when DIM=1 + { + *rParamsFile << mNormalToPlane[index] << ","; + } + *rParamsFile << mNormalToPlane[SPACE_DIM - 1] << "\n"; + *rParamsFile << "\t\t\t" << mUseJiggledNodesOnPlane << "\n"; + + // Call method on direct parent class + AbstractCellPopulationBoundaryCondition::OutputCellPopulationBoundaryConditionParameters(rParamsFile); +} + +// Explicit instantiation +template class AttractingPlaneBoundaryCondition<1, 1>; +template class AttractingPlaneBoundaryCondition<1, 2>; +template class AttractingPlaneBoundaryCondition<2, 2>; +template class AttractingPlaneBoundaryCondition<1, 3>; +template class AttractingPlaneBoundaryCondition<2, 3>; +template class AttractingPlaneBoundaryCondition<3, 3>; + +// Serialization for Boost >= 1.36 +#include "SerializationExportWrapperForCpp.hpp" +EXPORT_TEMPLATE_CLASS_ALL_DIMS(AttractingPlaneBoundaryCondition) diff --git a/pychaste/src/cpp/cell_based/AttractingPlaneBoundaryCondition.hpp b/pychaste/src/cpp/cell_based/AttractingPlaneBoundaryCondition.hpp new file mode 100644 index 0000000000..e808cdb405 --- /dev/null +++ b/pychaste/src/cpp/cell_based/AttractingPlaneBoundaryCondition.hpp @@ -0,0 +1,223 @@ +/* + +Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef ATTRACTINGPLANEBOUNDARYCONDITION_HPP_ +#define ATTRACTINGPLANEBOUNDARYCONDITION_HPP_ + +#include "AbstractCellPopulationBoundaryCondition.hpp" + +#include "ChasteSerialization.hpp" +#include +#include + +/** + * A plane cell population boundary condition class, which pulls nodes toward a specified plane. + * The plane can move over the course of the simulation, allowing simulated tensile tests. + * Although the name of this class suggests it is specific to 3D, it is actually also implemented in 2D, for which it is + * really a 'line' boundary condition. It's not currently implemented in 1D + */ +template +class AttractingPlaneBoundaryCondition : public AbstractCellPopulationBoundaryCondition +{ +private: + /** + * A point on the boundary plane. + */ + c_vector mPointOnPlane; + + /** + * The outward-facing unit normal vector to the boundary plane. + */ + c_vector mNormalToPlane; + + /** + * Whether to jiggle the cells on the bottom surface, initialised to false + * in the constructor. + */ + bool mUseJiggledNodesOnPlane; + + /** + * Nodes inside this distance to the plane are snapped to the plane + */ + double mAttractionThreshold; + + /** Needed for serialization. */ + friend class boost::serialization::access; + /** + * Serialize the object and its member variables. + * + * @param archive the archive + * @param version the current version of this class + */ + template + void serialize(Archive& archive, const unsigned int version) + { + archive& boost::serialization::base_object >(*this); + // archive & mUseJiggledNodesOnPlane; + } + +public: + /** + * Constructor. + * + * @param pCellPopulation pointer to the cell population + * @param point a point on the boundary plane + * @param normal the outward-facing unit normal vector to the boundary plane + */ + AttractingPlaneBoundaryCondition(AbstractCellPopulation* pCellPopulation, + c_vector point, c_vector normal); + + /** + * @return #mPointOnPlane. + */ + const c_vector& rGetPointOnPlane() const; + + /** + * @return #mNormalToPlane. + */ + const c_vector& rGetNormalToPlane() const; + + /** + * Set method for mUseJiggledNodesOnPlane + * + * @param useJiggledNodesOnPlane whether to jiggle the nodes on the surface of the plane, can help stop overcrowding on plane. + */ + void SetUseJiggledNodesOnPlane(bool useJiggledNodesOnPlane); + + /** @return #mUseJiggledNodesOnPlane. */ + bool GetUseJiggledNodesOnPlane(); + + /** + * Overridden ImposeBoundaryCondition() method. + * + * Apply the cell population boundary conditions. + * + * @param rOldLocations the node locations before any boundary conditions are applied + */ + void ImposeBoundaryCondition(const std::map*, c_vector >& rOldLocations); + + /** + * Overridden VerifyBoundaryCondition() method. + * Verify the boundary conditions have been applied. + * This is called after ImposeBoundaryCondition() to ensure the condition is still satisfied. + * + * @return whether the boundary conditions are satisfied. + */ + bool VerifyBoundaryCondition(); + + /** + * Overridden OutputCellPopulationBoundaryConditionParameters() method. + * Output cell population boundary condition parameters to file. + * + * @param rParamsFile the file stream to which the parameters are output + */ + void OutputCellPopulationBoundaryConditionParameters(out_stream& rParamsFile); + + /** + * Give a new value to the point describing the plane, can be used for time varying boundary conditions + * + * @param point a point on the boundary plane + */ + void SetPointOnPlane(const c_vector& rPoint); + + /** + * Specify the distance to the plane for cell attraction + * + * @param attractionThreshold the distance to the plane for cell attraction + */ + void SetAttractionThreshold(double attractionThreshold); +}; + +#include "SerializationExportWrapper.hpp" +EXPORT_TEMPLATE_CLASS_ALL_DIMS(AttractingPlaneBoundaryCondition) + +namespace boost +{ +namespace serialization +{ + /** + * Serialize information required to construct a AttractingPlaneBoundaryCondition. + */ + template + inline void save_construct_data( + Archive& ar, const AttractingPlaneBoundaryCondition* t, const unsigned int file_version) + { + // Save data required to construct instance + const AbstractCellPopulation* const p_cell_population = t->GetCellPopulation(); + ar << p_cell_population; + + // Archive c_vectors one component at a time + c_vector point = t->rGetPointOnPlane(); + for (unsigned i = 0; i < SPACE_DIM; i++) + { + ar << point[i]; + } + c_vector normal = t->rGetNormalToPlane(); + for (unsigned i = 0; i < SPACE_DIM; i++) + { + ar << normal[i]; + } + } + + /** + * De-serialize constructor parameters and initialize a AttractingPlaneBoundaryCondition. + */ + template + inline void load_construct_data( + Archive& ar, AttractingPlaneBoundaryCondition* t, const unsigned int file_version) + { + // Retrieve data from archive required to construct new instance + AbstractCellPopulation* p_cell_population; + ar >> p_cell_population; + + // Archive c_vectors one component at a time + c_vector point; + for (unsigned i = 0; i < SPACE_DIM; i++) + { + ar >> point[i]; + } + c_vector normal; + for (unsigned i = 0; i < SPACE_DIM; i++) + { + ar >> normal[i]; + } + + // Invoke inplace constructor to initialise instance + ::new (t) AttractingPlaneBoundaryCondition(p_cell_population, point, normal); + } +} // namespace serialization +} // namespace boost + +#endif // ATTRACTINGPLANEBOUNDARYCONDITION_HPP_ diff --git a/pychaste/src/cpp/cell_based/PythonSimulationModifier.cpp b/pychaste/src/cpp/cell_based/PythonSimulationModifier.cpp new file mode 100644 index 0000000000..e2f38e28ce --- /dev/null +++ b/pychaste/src/cpp/cell_based/PythonSimulationModifier.cpp @@ -0,0 +1,85 @@ +/* + +Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "PythonSimulationModifier.hpp" + +template +PythonSimulationModifier::PythonSimulationModifier() + : AbstractCellBasedSimulationModifier() +{ +} + +template +PythonSimulationModifier::~PythonSimulationModifier() +{ +} + +template +void PythonSimulationModifier::UpdateAtEndOfTimeStep(AbstractCellPopulation& rCellPopulation) +{ + UpdateCellData(rCellPopulation); +} + +template +void PythonSimulationModifier::SetupSolve(AbstractCellPopulation& rCellPopulation, std::string outputDirectory) +{ + /* + * We must update CellData in SetupSolve(), otherwise it will not have been + * fully initialised by the time we enter the main time loop. + */ + UpdateCellData(rCellPopulation); +} + +template +void PythonSimulationModifier::UpdateCellData(AbstractCellPopulation& rCellPopulation) +{ + // Make sure the cell population is updated + rCellPopulation.Update(); +} + +template +void PythonSimulationModifier::OutputSimulationModifierParameters(out_stream& rParamsFile) +{ + // No parameters to output, so just call method on direct parent class + AbstractCellBasedSimulationModifier::OutputSimulationModifierParameters(rParamsFile); +} + +// Explicit instantiation +template class PythonSimulationModifier<2>; +template class PythonSimulationModifier<3>; + +// Serialization for Boost >= 1.36 +#include "SerializationExportWrapperForCpp.hpp" +EXPORT_TEMPLATE_CLASS_SAME_DIMS(PythonSimulationModifier) diff --git a/pychaste/src/cpp/cell_based/PythonSimulationModifier.hpp b/pychaste/src/cpp/cell_based/PythonSimulationModifier.hpp new file mode 100644 index 0000000000..4b64b474c3 --- /dev/null +++ b/pychaste/src/cpp/cell_based/PythonSimulationModifier.hpp @@ -0,0 +1,121 @@ +/* + +Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef PYTHONSIMULATIONMODIFIER_HPP_ +#define PYTHONSIMULATIONMODIFIER_HPP_ + +#include "ChasteSerialization.hpp" +#include + +#include "AbstractCellBasedSimulationModifier.hpp" +#include "VtkScene.hpp" + +/** + * Update a vtk scene at each simulation time step. + */ +template +class PythonSimulationModifier : public AbstractCellBasedSimulationModifier +{ + /** Needed for serialization. */ + friend class boost::serialization::access; + /** + * Boost Serialization method for archiving/checkpointing. + * Archives the object and its member variables. + * + * @param archive The boost archive. + * @param version The current version of this class. + */ + template + void serialize(Archive & archive, const unsigned int version) + { + archive & boost::serialization::base_object >(*this); + } + +public: + + /** + * Default constructor. + */ + PythonSimulationModifier(); + + /** + * Destructor. + */ + virtual ~PythonSimulationModifier(); + + /** + * Overridden UpdateAtEndOfTimeStep() method. + * + * Specifies what to do in the simulation at the end of each time step. + * + * @param rCellPopulation reference to the cell population + */ + virtual void UpdateAtEndOfTimeStep(AbstractCellPopulation& rCellPopulation); + + /** + * Overridden SetupSolve() method. + * + * Specifies what to do in the simulation before the start of the time loop. + * + * @param rCellPopulation reference to the cell population + * @param outputDirectory the output directory, relative to where Chaste output is stored + */ + virtual void SetupSolve(AbstractCellPopulation& rCellPopulation, std::string outputDirectory); + + /** + * Helper method to compute the mean level of Delta in each cell's neighbours and store these in the CellData. + * + * Note: If using a CaBasedCellPopulation, we assume a Moore neighbourhood and unit carrying capacity. + * If a cell has no neighbours (such as an isolated cell in a CaBasedCellPopulation), we store the + * value -1 in the CellData. + * + * @param rCellPopulation reference to the cell population + */ + virtual void UpdateCellData(AbstractCellPopulation& rCellPopulation); + + /** + * Overridden OutputSimulationModifierParameters() method. + * Output any simulation modifier parameters to file. + * + * @param rParamsFile the file stream to which the parameters are output + */ + void OutputSimulationModifierParameters(out_stream& rParamsFile); + +}; + +#include "SerializationExportWrapper.hpp" +EXPORT_TEMPLATE_CLASS_SAME_DIMS(PythonSimulationModifier) + +#endif // PYTHONSIMULATIONMODIFIER_HPP_ diff --git a/pychaste/src/cpp/cell_based/VtkSceneModifier.cpp b/pychaste/src/cpp/cell_based/VtkSceneModifier.cpp new file mode 100644 index 0000000000..f702cb7655 --- /dev/null +++ b/pychaste/src/cpp/cell_based/VtkSceneModifier.cpp @@ -0,0 +1,135 @@ +/* + +Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "VtkSceneModifier.hpp" + +template +VtkSceneModifier::VtkSceneModifier() + : AbstractCellBasedSimulationModifier(), + mpScene(), + mUpdateFrequency(1) +{ +} + +template +VtkSceneModifier::~VtkSceneModifier() +{ +} + +// 1-D is not supported +template<> +void VtkSceneModifier<1>::UpdateAtEndOfTimeStep(AbstractCellPopulation<1,1>& rCellPopulation) +{ + UpdateCellData(rCellPopulation); +} + +template +void VtkSceneModifier::UpdateAtEndOfTimeStep(AbstractCellPopulation& rCellPopulation) +{ + UpdateCellData(rCellPopulation); + + if(DIM>1) + { + if(mpScene and SimulationTime::Instance()->GetTimeStepsElapsed()%mUpdateFrequency==0) + { + mpScene->ResetRenderer(SimulationTime::Instance()->GetTimeStepsElapsed()); + } + } +} + +template +boost::shared_ptr > VtkSceneModifier::GetVtkScene() +{ + return mpScene; +} + +template +void VtkSceneModifier::SetVtkScene(boost::shared_ptr > pScene) +{ + mpScene = pScene; +} + +// 1-D is not supported +template<> +void VtkSceneModifier<1>::SetupSolve(AbstractCellPopulation<1,1>& rCellPopulation, std::string outputDirectory) +{ + /* + * We must update CellData in SetupSolve(), otherwise it will not have been + * fully initialised by the time we enter the main time loop. + */ + UpdateCellData(rCellPopulation); +} + +template +void VtkSceneModifier::SetupSolve(AbstractCellPopulation& rCellPopulation, std::string outputDirectory) +{ + /* + * We must update CellData in SetupSolve(), otherwise it will not have been + * fully initialised by the time we enter the main time loop. + */ + UpdateCellData(rCellPopulation); + if(mpScene and SimulationTime::Instance()->GetTimeStepsElapsed()%mUpdateFrequency==0) + { + mpScene->ResetRenderer(SimulationTime::Instance()->GetTimeStepsElapsed()); + } +} + +template +void VtkSceneModifier::UpdateCellData(AbstractCellPopulation& rCellPopulation) +{ + // Make sure the cell population is updated + rCellPopulation.Update(); +} + +template +void VtkSceneModifier::OutputSimulationModifierParameters(out_stream& rParamsFile) +{ + // No parameters to output, so just call method on direct parent class + AbstractCellBasedSimulationModifier::OutputSimulationModifierParameters(rParamsFile); +} + +template +void VtkSceneModifier::SetUpdateFrequency(unsigned frequency) +{ + mUpdateFrequency = frequency; +} + +// Explicit instantiation +template class VtkSceneModifier<2>; +template class VtkSceneModifier<3>; + +// Serialization for Boost >= 1.36 +#include "SerializationExportWrapperForCpp.hpp" +EXPORT_TEMPLATE_CLASS_SAME_DIMS(VtkSceneModifier) diff --git a/pychaste/src/cpp/cell_based/VtkSceneModifier.hpp b/pychaste/src/cpp/cell_based/VtkSceneModifier.hpp new file mode 100644 index 0000000000..ce6dcb32a8 --- /dev/null +++ b/pychaste/src/cpp/cell_based/VtkSceneModifier.hpp @@ -0,0 +1,148 @@ +/* + +Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef VTKSCENEMODIFIER_HPP_ +#define VTKSCENEMODIFIER_HPP_ + +#include "ChasteSerialization.hpp" +#include + +#include "AbstractCellBasedSimulationModifier.hpp" +#include "VtkScene.hpp" + +/** + * Update a vtk scene at each simulation time step. + */ +template +class VtkSceneModifier : public AbstractCellBasedSimulationModifier +{ + /** Needed for serialization. */ + friend class boost::serialization::access; + /** + * Boost Serialization method for archiving/checkpointing. + * Archives the object and its member variables. + * + * @param archive The boost archive. + * @param version The current version of this class. + */ + template + void serialize(Archive & archive, const unsigned int version) + { + archive & boost::serialization::base_object >(*this); + } + + /** + * The scene + */ + boost::shared_ptr > mpScene; + + /** + * The scene update frequency + */ + unsigned mUpdateFrequency; + +public: + + /** + * Default constructor. + */ + VtkSceneModifier(); + + /** + * Destructor. + */ + virtual ~VtkSceneModifier(); + + /** + * Get the scene + * @return the scene + */ + boost::shared_ptr > GetVtkScene(); + + /** + * Overridden UpdateAtEndOfTimeStep() method. + * + * Specifies what to do in the simulation at the end of each time step. + * + * @param rCellPopulation reference to the cell population + */ + virtual void UpdateAtEndOfTimeStep(AbstractCellPopulation& rCellPopulation); + + /** + * Overridden SetupSolve() method. + * + * Specifies what to do in the simulation before the start of the time loop. + * + * @param rCellPopulation reference to the cell population + * @param outputDirectory the output directory, relative to where Chaste output is stored + */ + virtual void SetupSolve(AbstractCellPopulation& rCellPopulation, std::string outputDirectory); + + /** + * Set the scene + * @param pScene the scene + */ + void SetVtkScene(boost::shared_ptr > pScene); + + /** + * Helper method to compute the mean level of Delta in each cell's neighbours and store these in the CellData. + * + * Note: If using a CaBasedCellPopulation, we assume a Moore neighbourhood and unit carrying capacity. + * If a cell has no neighbours (such as an isolated cell in a CaBasedCellPopulation), we store the + * value -1 in the CellData. + * + * @param rCellPopulation reference to the cell population + */ + void UpdateCellData(AbstractCellPopulation& rCellPopulation); + + /** + * Overridden OutputSimulationModifierParameters() method. + * Output any simulation modifier parameters to file. + * + * @param rParamsFile the file stream to which the parameters are output + */ + void OutputSimulationModifierParameters(out_stream& rParamsFile); + + /** + * Set the frequency of output + * @param frequency the frequency of output + */ + void SetUpdateFrequency(unsigned frequency); +}; + +#include "SerializationExportWrapper.hpp" +EXPORT_TEMPLATE_CLASS_SAME_DIMS(VtkSceneModifier) + +#endif // VTKSCENEMODIFIER_HPP_ diff --git a/pychaste/src/cpp/visualization/AbstractPyChasteActorGenerator.cpp b/pychaste/src/cpp/visualization/AbstractPyChasteActorGenerator.cpp new file mode 100644 index 0000000000..68d3117d2b --- /dev/null +++ b/pychaste/src/cpp/visualization/AbstractPyChasteActorGenerator.cpp @@ -0,0 +1,720 @@ +/* + +Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Exception.hpp" +#include "UblasIncludes.hpp" +#include "UblasVectorInclude.hpp" + +#include "AbstractPyChasteActorGenerator.hpp" + +template +AbstractPyChasteActorGenerator::AbstractPyChasteActorGenerator() + : mpColorTransferFunction(vtkSmartPointer::New()), + mpDiscreteColorTransferFunction(vtkSmartPointer::New()), + mShowEdges(true), + mShowPoints(false), + mShowVolume(true), + mEdgeColor(zero_vector(3)), + mPointColor(unit_vector(3, 2)), + mVolumeColor(unit_vector(3, 0)), + mVolumeOpacity(0.9), + mPointSize(0.5), + mEdgeSize(0.01), + mDataLabel(), + mpScaleBar(vtkSmartPointer::New()), + mShowScaleBar(false) +{ + mPointColor *= 255.0; + mVolumeColor *= 255.0; + + double viridis_colors[256][3] = { { 0.0267004, 0.004874, 0.329415 }, + { 0.0268510, 0.009605, 0.335427 }, + { 0.0269944, 0.014625, 0.341379 }, + { 0.0271305, 0.019942, 0.347269 }, + { 0.0272594, 0.025563, 0.353093 }, + { 0.0273809, 0.031497, 0.358853 }, + { 0.0274952, 0.037752, 0.364543 }, + { 0.0276022, 0.044167, 0.370164 }, + { 0.0277018, 0.050344, 0.375715 }, + { 0.0277941, 0.056324, 0.381191 }, + { 0.0278791, 0.062145, 0.386592 }, + { 0.0279566, 0.067836, 0.391917 }, + { 0.0280267, 0.073417, 0.397163 }, + { 0.0280894, 0.078907, 0.402329 }, + { 0.0281446, 0.084320, 0.407414 }, + { 0.0281924, 0.089666, 0.412415 }, + { 0.0282327, 0.094955, 0.417331 }, + { 0.0282656, 0.100196, 0.422160 }, + { 0.0282910, 0.105393, 0.426902 }, + { 0.0283091, 0.110553, 0.431554 }, + { 0.0283197, 0.115680, 0.436115 }, + { 0.0283229, 0.120777, 0.440584 }, + { 0.0283187, 0.125848, 0.444960 }, + { 0.0283072, 0.130895, 0.449241 }, + { 0.0282884, 0.135920, 0.453427 }, + { 0.0282623, 0.140926, 0.457517 }, + { 0.0282290, 0.145912, 0.461510 }, + { 0.0281887, 0.150881, 0.465405 }, + { 0.0281412, 0.155834, 0.469201 }, + { 0.0280868, 0.160771, 0.472899 }, + { 0.0280255, 0.165693, 0.476498 }, + { 0.0279574, 0.170599, 0.479997 }, + { 0.0278826, 0.175490, 0.483397 }, + { 0.0278012, 0.180367, 0.486697 }, + { 0.0277134, 0.185228, 0.489898 }, + { 0.0276194, 0.190074, 0.493001 }, + { 0.0275191, 0.194905, 0.496005 }, + { 0.0274128, 0.199721, 0.498911 }, + { 0.0273006, 0.204520, 0.501721 }, + { 0.0271828, 0.209303, 0.504434 }, + { 0.0270595, 0.214069, 0.507052 }, + { 0.0269308, 0.218818, 0.509577 }, + { 0.0267968, 0.223549, 0.512008 }, + { 0.0266580, 0.228262, 0.514349 }, + { 0.0265145, 0.232956, 0.516599 }, + { 0.0263663, 0.237631, 0.518762 }, + { 0.0262138, 0.242286, 0.520837 }, + { 0.0260571, 0.246922, 0.522828 }, + { 0.0258965, 0.251537, 0.524736 }, + { 0.0257322, 0.256130, 0.526563 }, + { 0.0255645, 0.260703, 0.528312 }, + { 0.0253935, 0.265254, 0.529983 }, + { 0.0252194, 0.269783, 0.531579 }, + { 0.0250425, 0.274290, 0.533103 }, + { 0.0248629, 0.278775, 0.534556 }, + { 0.0246811, 0.283237, 0.535941 }, + { 0.0244972, 0.287675, 0.537260 }, + { 0.0243113, 0.292092, 0.538516 }, + { 0.0241237, 0.296485, 0.539709 }, + { 0.0239346, 0.300855, 0.540844 }, + { 0.0237441, 0.305202, 0.541921 }, + { 0.0235526, 0.309527, 0.542944 }, + { 0.0233603, 0.313828, 0.543914 }, + { 0.0231674, 0.318106, 0.544834 }, + { 0.0229739, 0.322361, 0.545706 }, + { 0.0227802, 0.326594, 0.546532 }, + { 0.0225863, 0.330805, 0.547314 }, + { 0.0223925, 0.334994, 0.548053 }, + { 0.0221989, 0.339161, 0.548752 }, + { 0.0220057, 0.343307, 0.549413 }, + { 0.0218130, 0.347432, 0.550038 }, + { 0.0216210, 0.351535, 0.550627 }, + { 0.0214298, 0.355619, 0.551184 }, + { 0.0212395, 0.359683, 0.551710 }, + { 0.0210503, 0.363727, 0.552206 }, + { 0.0208623, 0.367752, 0.552675 }, + { 0.0206756, 0.371758, 0.553117 }, + { 0.0204903, 0.375746, 0.553533 }, + { 0.0203063, 0.379716, 0.553925 }, + { 0.0201239, 0.383670, 0.554294 }, + { 0.0199430, 0.387607, 0.554642 }, + { 0.0197636, 0.391528, 0.554969 }, + { 0.0195860, 0.395433, 0.555276 }, + { 0.0194100, 0.399323, 0.555565 }, + { 0.0192357, 0.403199, 0.555836 }, + { 0.0190631, 0.407061, 0.556089 }, + { 0.0188923, 0.410910, 0.556326 }, + { 0.0187231, 0.414746, 0.556547 }, + { 0.0185556, 0.418570, 0.556753 }, + { 0.0183898, 0.422383, 0.556944 }, + { 0.0182256, 0.426184, 0.557120 }, + { 0.0180629, 0.429975, 0.557282 }, + { 0.0179019, 0.433756, 0.557430 }, + { 0.0177423, 0.437527, 0.557565 }, + { 0.0175841, 0.441290, 0.557685 }, + { 0.0174274, 0.445044, 0.557792 }, + { 0.0172719, 0.448791, 0.557885 }, + { 0.0171176, 0.452530, 0.557965 }, + { 0.0169646, 0.456262, 0.558030 }, + { 0.0168126, 0.459988, 0.558082 }, + { 0.0166617, 0.463708, 0.558119 }, + { 0.0165117, 0.467423, 0.558141 }, + { 0.0163625, 0.471133, 0.558148 }, + { 0.0162142, 0.474838, 0.558140 }, + { 0.0160665, 0.478540, 0.558115 }, + { 0.0159194, 0.482237, 0.558073 }, + { 0.0157729, 0.485932, 0.558013 }, + { 0.0156270, 0.489624, 0.557936 }, + { 0.0154815, 0.493313, 0.557840 }, + { 0.0153364, 0.497000, 0.557724 }, + { 0.0151918, 0.500685, 0.557587 }, + { 0.0150476, 0.504369, 0.557430 }, + { 0.0149039, 0.508051, 0.557250 }, + { 0.0147607, 0.511733, 0.557049 }, + { 0.0146180, 0.515413, 0.556823 }, + { 0.0144759, 0.519093, 0.556572 }, + { 0.0143343, 0.522773, 0.556295 }, + { 0.0141935, 0.526453, 0.555991 }, + { 0.0140536, 0.530132, 0.555659 }, + { 0.0139147, 0.533812, 0.555298 }, + { 0.0137770, 0.537492, 0.554906 }, + { 0.0136408, 0.541173, 0.554483 }, + { 0.0135066, 0.544853, 0.554029 }, + { 0.0133743, 0.548535, 0.553541 }, + { 0.0132444, 0.552216, 0.553018 }, + { 0.0131172, 0.555899, 0.552459 }, + { 0.0129933, 0.559582, 0.551864 }, + { 0.0128729, 0.563265, 0.551229 }, + { 0.0127568, 0.566949, 0.550556 }, + { 0.0126453, 0.570633, 0.549841 }, + { 0.0125394, 0.574318, 0.549086 }, + { 0.0124395, 0.578002, 0.548287 }, + { 0.0123463, 0.581687, 0.547445 }, + { 0.0122606, 0.585371, 0.546557 }, + { 0.0121831, 0.589055, 0.545623 }, + { 0.0121148, 0.592739, 0.544641 }, + { 0.0120565, 0.596422, 0.543611 }, + { 0.0120092, 0.600104, 0.542530 }, + { 0.0119738, 0.603785, 0.541400 }, + { 0.0119512, 0.607464, 0.540218 }, + { 0.0119423, 0.611141, 0.538982 }, + { 0.0119483, 0.614817, 0.537692 }, + { 0.0119699, 0.618490, 0.536347 }, + { 0.0120081, 0.622161, 0.534946 }, + { 0.0120638, 0.625828, 0.533488 }, + { 0.0121380, 0.629492, 0.531973 }, + { 0.0122312, 0.633153, 0.530398 }, + { 0.0123444, 0.636809, 0.528763 }, + { 0.0124780, 0.640461, 0.527068 }, + { 0.0126326, 0.644107, 0.525311 }, + { 0.0128087, 0.647749, 0.523491 }, + { 0.0130067, 0.651384, 0.521608 }, + { 0.0132268, 0.655014, 0.519661 }, + { 0.0134692, 0.658636, 0.517649 }, + { 0.0137339, 0.662252, 0.515571 }, + { 0.0140210, 0.665859, 0.513427 }, + { 0.0143303, 0.669459, 0.511215 }, + { 0.0146616, 0.673050, 0.508936 }, + { 0.0150148, 0.676631, 0.506589 }, + { 0.0153894, 0.680203, 0.504172 }, + { 0.0157851, 0.683765, 0.501686 }, + { 0.0162016, 0.687316, 0.499129 }, + { 0.0166383, 0.690856, 0.496502 }, + { 0.0170948, 0.694384, 0.493803 }, + { 0.0175707, 0.697900, 0.491033 }, + { 0.0180653, 0.701402, 0.488189 }, + { 0.0185783, 0.704891, 0.485273 }, + { 0.0191090, 0.708366, 0.482284 }, + { 0.0196571, 0.711827, 0.479221 }, + { 0.0202219, 0.715272, 0.476084 }, + { 0.0208030, 0.718701, 0.472873 }, + { 0.0214000, 0.722114, 0.469588 }, + { 0.0220124, 0.725509, 0.466226 }, + { 0.0226397, 0.728888, 0.462789 }, + { 0.0232815, 0.732247, 0.459277 }, + { 0.0239374, 0.735588, 0.455688 }, + { 0.0246070, 0.738910, 0.452024 }, + { 0.0252899, 0.742211, 0.448284 }, + { 0.0259857, 0.745492, 0.444467 }, + { 0.0266941, 0.748751, 0.440573 }, + { 0.0274149, 0.751988, 0.436601 }, + { 0.0281477, 0.755203, 0.432552 }, + { 0.0288921, 0.758394, 0.428426 }, + { 0.0296479, 0.761561, 0.424223 }, + { 0.0304148, 0.764704, 0.419943 }, + { 0.0311925, 0.767822, 0.415586 }, + { 0.0319809, 0.770914, 0.411152 }, + { 0.0327796, 0.773980, 0.406640 }, + { 0.0335885, 0.777018, 0.402049 }, + { 0.0344074, 0.780029, 0.397381 }, + { 0.0352360, 0.783011, 0.392636 }, + { 0.0360741, 0.785964, 0.387814 }, + { 0.0369214, 0.788888, 0.382914 }, + { 0.0377779, 0.791781, 0.377939 }, + { 0.0386433, 0.794644, 0.372886 }, + { 0.0395174, 0.797475, 0.367757 }, + { 0.0404001, 0.800275, 0.362552 }, + { 0.0412913, 0.803041, 0.357269 }, + { 0.0421908, 0.805774, 0.351910 }, + { 0.0430983, 0.808473, 0.346476 }, + { 0.0440137, 0.811138, 0.340967 }, + { 0.0449368, 0.813768, 0.335384 }, + { 0.0458674, 0.816363, 0.329727 }, + { 0.0468053, 0.818921, 0.323998 }, + { 0.0477504, 0.821444, 0.318195 }, + { 0.0487026, 0.823929, 0.312321 }, + { 0.0496615, 0.826376, 0.306377 }, + { 0.0506271, 0.828786, 0.300362 }, + { 0.0515992, 0.831158, 0.294279 }, + { 0.0525776, 0.833491, 0.288127 }, + { 0.0535621, 0.835785, 0.281908 }, + { 0.0545524, 0.838039, 0.275626 }, + { 0.0555484, 0.840254, 0.269281 }, + { 0.0565498, 0.842430, 0.262877 }, + { 0.0575563, 0.844566, 0.256415 }, + { 0.0585678, 0.846661, 0.249897 }, + { 0.0595839, 0.848717, 0.243329 }, + { 0.0606045, 0.850733, 0.236712 }, + { 0.0616293, 0.852709, 0.230052 }, + { 0.0626579, 0.854645, 0.223353 }, + { 0.0636902, 0.856542, 0.216620 }, + { 0.0647257, 0.858400, 0.209861 }, + { 0.0657642, 0.860219, 0.203082 }, + { 0.0668054, 0.861999, 0.196293 }, + { 0.0678489, 0.863742, 0.189503 }, + { 0.0688944, 0.865448, 0.182725 }, + { 0.0699415, 0.867117, 0.175971 }, + { 0.0709898, 0.868751, 0.169257 }, + { 0.0720391, 0.870350, 0.162603 }, + { 0.0730889, 0.871916, 0.156029 }, + { 0.0741388, 0.873449, 0.149561 }, + { 0.0751884, 0.874951, 0.143228 }, + { 0.0762373, 0.876424, 0.137064 }, + { 0.0772852, 0.877868, 0.131109 }, + { 0.0783315, 0.879285, 0.125405 }, + { 0.0793760, 0.880678, 0.120005 }, + { 0.0804182, 0.882046, 0.114965 }, + { 0.0814576, 0.883393, 0.110347 }, + { 0.0824940, 0.884720, 0.106217 }, + { 0.0835270, 0.886029, 0.102646 }, + { 0.0845561, 0.887322, 0.099702 }, + { 0.0855810, 0.888601, 0.097452 }, + { 0.0866013, 0.889868, 0.095953 }, + { 0.0876168, 0.891125, 0.095250 }, + { 0.0886271, 0.892374, 0.095374 }, + { 0.0896320, 0.893616, 0.096335 }, + { 0.0906311, 0.894855, 0.098125 }, + { 0.0916242, 0.896091, 0.100717 }, + { 0.0926106, 0.897330, 0.104071 }, + { 0.0935904, 0.898570, 0.108131 }, + { 0.0945636, 0.899815, 0.112838 }, + { 0.0955300, 0.901065, 0.118128 }, + { 0.0964894, 0.902323, 0.123941 }, + { 0.0974417, 0.903590, 0.130215 }, + { 0.0983868, 0.904867, 0.136897 }, + { 0.0993248, 0.906157, 0.143936 } }; + + for (unsigned idx = 0; idx < 256; idx++) + { + mpColorTransferFunction->AddRGBPoint(double(idx) / 255.0, + viridis_colors[idx][0], + viridis_colors[idx][1], + viridis_colors[idx][2]); + } + + double accent_colors[256][3] = { + { 0.498039215803, 0.78823530674, 0.498039215803 }, + { 0.504821223137, 0.785328732519, 0.507189542873 }, + { 0.511603230472, 0.782422158297, 0.516339869943 }, + { 0.518385237806, 0.779515584076, 0.525490197013 }, + { 0.52516724514, 0.776609009855, 0.534640524083 }, + { 0.531949252475, 0.773702435634, 0.543790851154 }, + { 0.538731259809, 0.770795861412, 0.552941178224 }, + { 0.545513267143, 0.767889287191, 0.562091505294 }, + { 0.552295274477, 0.76498271297, 0.571241832364 }, + { 0.559077281812, 0.762076138749, 0.580392159434 }, + { 0.565859289146, 0.759169564528, 0.589542486504 }, + { 0.57264129648, 0.756262990306, 0.598692813574 }, + { 0.579423303814, 0.753356416085, 0.607843140644 }, + { 0.586205311149, 0.750449841864, 0.616993467714 }, + { 0.592987318483, 0.747543267643, 0.626143794784 }, + { 0.599769325817, 0.744636693422, 0.635294121854 }, + { 0.606551333152, 0.7417301192, 0.644444448925 }, + { 0.613333340486, 0.738823544979, 0.653594775995 }, + { 0.62011534782, 0.735916970758, 0.662745103065 }, + { 0.626897355154, 0.733010396537, 0.671895430135 }, + { 0.633679362489, 0.730103822315, 0.681045757205 }, + { 0.640461369823, 0.727197248094, 0.690196084275 }, + { 0.647243377157, 0.724290673873, 0.699346411345 }, + { 0.654025384492, 0.721384099652, 0.708496738415 }, + { 0.660807391826, 0.718477525431, 0.717647065485 }, + { 0.66758939916, 0.715570951209, 0.726797392555 }, + { 0.674371406494, 0.712664376988, 0.735947719625 }, + { 0.681153413829, 0.709757802767, 0.745098046695 }, + { 0.687935421163, 0.706851228546, 0.754248373766 }, + { 0.694717428497, 0.703944654324, 0.763398700836 }, + { 0.701499435832, 0.701038080103, 0.772549027906 }, + { 0.708281443166, 0.698131505882, 0.781699354976 }, + { 0.7150634505, 0.695224931661, 0.790849682046 }, + { 0.721845457834, 0.69231835744, 0.800000009116 }, + { 0.728627465169, 0.689411783218, 0.809150336186 }, + { 0.735409472503, 0.686505208997, 0.818300663256 }, + { 0.742191479837, 0.683598634776, 0.827450990326 }, + { 0.748973486704, 0.68346022648, 0.826574404801 }, + { 0.75575549322, 0.685397942627, 0.818177634828 }, + { 0.762537499736, 0.687335658775, 0.809780864856 }, + { 0.769319506253, 0.689273374922, 0.801384094883 }, + { 0.776101512769, 0.69121109107, 0.792987324911 }, + { 0.782883519285, 0.693148807217, 0.784590554939 }, + { 0.789665525801, 0.695086523365, 0.776193784966 }, + { 0.796447532317, 0.697024239512, 0.767797014994 }, + { 0.803229538833, 0.69896195566, 0.759400245021 }, + { 0.81001154535, 0.700899671807, 0.751003475049 }, + { 0.816793551866, 0.702837387954, 0.742606705077 }, + { 0.823575558382, 0.704775104102, 0.734209935104 }, + { 0.830357564898, 0.706712820249, 0.725813165132 }, + { 0.837139571414, 0.708650536397, 0.717416395159 }, + { 0.84392157793, 0.710588252544, 0.709019625187 }, + { 0.850703584447, 0.712525968692, 0.700622855215 }, + { 0.857485590963, 0.714463684839, 0.692226085242 }, + { 0.864267597479, 0.716401400987, 0.68382931527 }, + { 0.871049603995, 0.718339117134, 0.675432545297 }, + { 0.877831610511, 0.720276833282, 0.667035775325 }, + { 0.884613617028, 0.722214549429, 0.658639005352 }, + { 0.891395623544, 0.724152265577, 0.65024223538 }, + { 0.89817763006, 0.726089981724, 0.641845465408 }, + { 0.904959636576, 0.728027697872, 0.633448695435 }, + { 0.911741643092, 0.729965414019, 0.625051925463 }, + { 0.918523649608, 0.731903130167, 0.61665515549 }, + { 0.925305656125, 0.733840846314, 0.608258385518 }, + { 0.932087662641, 0.735778562462, 0.599861615546 }, + { 0.938869669157, 0.737716278609, 0.591464845573 }, + { 0.945651675673, 0.739653994757, 0.583068075601 }, + { 0.952433682189, 0.741591710904, 0.574671305628 }, + { 0.959215688705, 0.743529427052, 0.566274535656 }, + { 0.965997695222, 0.745467143199, 0.557877765684 }, + { 0.972779701738, 0.747404859347, 0.549480995711 }, + { 0.979561708254, 0.749342575494, 0.541084225739 }, + { 0.98634371477, 0.751280291641, 0.532687455766 }, + { 0.992187620612, 0.75391004927, 0.525782419653 }, + { 0.992402922406, 0.760692055786, 0.527827786698 }, + { 0.9926182242, 0.767474062303, 0.529873153743 }, + { 0.992833525994, 0.774256068819, 0.531918520787 }, + { 0.993048827788, 0.781038075335, 0.533963887832 }, + { 0.993264129583, 0.787820081851, 0.536009254876 }, + { 0.993479431377, 0.794602088367, 0.538054621921 }, + { 0.993694733171, 0.801384094883, 0.540099988965 }, + { 0.993910034965, 0.8081661014, 0.54214535601 }, + { 0.994125336759, 0.814948107916, 0.544190723055 }, + { 0.994340638553, 0.821730114432, 0.546236090099 }, + { 0.994555940348, 0.828512120948, 0.548281457144 }, + { 0.994771242142, 0.835294127464, 0.550326824188 }, + { 0.994986543936, 0.84207613398, 0.552372191233 }, + { 0.99520184573, 0.848858140497, 0.554417558277 }, + { 0.995417147524, 0.855640147013, 0.556462925322 }, + { 0.995632449318, 0.862422153529, 0.558508292366 }, + { 0.995847751113, 0.869204160045, 0.560553659411 }, + { 0.996063052907, 0.875986166561, 0.562599026456 }, + { 0.996278354701, 0.882768173078, 0.5646443935 }, + { 0.996493656495, 0.889550179594, 0.566689760545 }, + { 0.996708958289, 0.89633218611, 0.568735127589 }, + { 0.996924260083, 0.903114192626, 0.570780494634 }, + { 0.997139561878, 0.909896199142, 0.572825861678 }, + { 0.997354863672, 0.916678205658, 0.574871228723 }, + { 0.997570165466, 0.923460212175, 0.576916595768 }, + { 0.99778546726, 0.930242218691, 0.578961962812 }, + { 0.998000769054, 0.937024225207, 0.581007329857 }, + { 0.998216070848, 0.943806231723, 0.583052696901 }, + { 0.998431372643, 0.950588238239, 0.585098063946 }, + { 0.998646674437, 0.957370244755, 0.58714343099 }, + { 0.998861976231, 0.964152251272, 0.589188798035 }, + { 0.999077278025, 0.970934257788, 0.591234165079 }, + { 0.999292579819, 0.977716264304, 0.593279532124 }, + { 0.999507881613, 0.98449827082, 0.595324899169 }, + { 0.999723183408, 0.991280277336, 0.597370266213 }, + { 0.999938485202, 0.998062283853, 0.599415633258 }, + { 0.984698193038, 0.988696655222, 0.601768574294 }, + { 0.963275663292, 0.972871972533, 0.604244544927 }, + { 0.941853133545, 0.957047289844, 0.60672051556 }, + { 0.920430603799, 0.941222607154, 0.609196486193 }, + { 0.899008074052, 0.925397924465, 0.611672456825 }, + { 0.877585544306, 0.909573241776, 0.614148427458 }, + { 0.856163014559, 0.893748559087, 0.616624398091 }, + { 0.834740484813, 0.877923876398, 0.619100368724 }, + { 0.813317955066, 0.862099193709, 0.621576339357 }, + { 0.79189542532, 0.846274511019, 0.62405230999 }, + { 0.770472895573, 0.83044982833, 0.626528280623 }, + { 0.749050365827, 0.814625145641, 0.629004251256 }, + { 0.72762783608, 0.798800462952, 0.631480221889 }, + { 0.706205306334, 0.782975780263, 0.633956192521 }, + { 0.684782776587, 0.767151097573, 0.636432163154 }, + { 0.663360246841, 0.751326414884, 0.638908133787 }, + { 0.641937717094, 0.735501732195, 0.64138410442 }, + { 0.620515187348, 0.719677049506, 0.643860075053 }, + { 0.599092657601, 0.703852366817, 0.646336045686 }, + { 0.577670127855, 0.688027684128, 0.648812016319 }, + { 0.556247598108, 0.672203001438, 0.651287986952 }, + { 0.534825068362, 0.656378318749, 0.653763957585 }, + { 0.513402538615, 0.64055363606, 0.656239928217 }, + { 0.491980008869, 0.624728953371, 0.65871589885 }, + { 0.470557479122, 0.608904270682, 0.661191869483 }, + { 0.449134949376, 0.593079587992, 0.663667840116 }, + { 0.427712419629, 0.577254905303, 0.666143810749 }, + { 0.406289889883, 0.561430222614, 0.668619781382 }, + { 0.384867360136, 0.545605539925, 0.671095752015 }, + { 0.36344483039, 0.529780857236, 0.673571722648 }, + { 0.342022300643, 0.513956174547, 0.676047693281 }, + { 0.320599770897, 0.498131491857, 0.678523663914 }, + { 0.29917724115, 0.482306809168, 0.680999634546 }, + { 0.277754711404, 0.466482126479, 0.683475605179 }, + { 0.256332181657, 0.45065744379, 0.685951575812 }, + { 0.234909651911, 0.434832761101, 0.688427546445 }, + { 0.225267206746, 0.420269131785, 0.688688984104 }, + { 0.245074973036, 0.408858135901, 0.683414089329 }, + { 0.264882739327, 0.397447140018, 0.678139194554 }, + { 0.284690505617, 0.386036144135, 0.672864299779 }, + { 0.304498271907, 0.374625148252, 0.667589405004 }, + { 0.324306038197, 0.363214152368, 0.662314510229 }, + { 0.344113804488, 0.351803156485, 0.657039615453 }, + { 0.363921570778, 0.340392160602, 0.651764720678 }, + { 0.383729337068, 0.328981164719, 0.646489825903 }, + { 0.403537103358, 0.317570168835, 0.641214931128 }, + { 0.423344869649, 0.306159172952, 0.635940036353 }, + { 0.443152635939, 0.294748177069, 0.630665141578 }, + { 0.462960402229, 0.283337181186, 0.625390246803 }, + { 0.48276816852, 0.271926185302, 0.620115352028 }, + { 0.50257593481, 0.260515189419, 0.614840457252 }, + { 0.5223837011, 0.249104193536, 0.609565562477 }, + { 0.54219146739, 0.237693197653, 0.604290667702 }, + { 0.561999233681, 0.226282201769, 0.599015772927 }, + { 0.581806999971, 0.214871205886, 0.593740878152 }, + { 0.601614766261, 0.203460210003, 0.588465983377 }, + { 0.621422532551, 0.19204921412, 0.583191088602 }, + { 0.641230298842, 0.180638218236, 0.577916193827 }, + { 0.661038065132, 0.169227222353, 0.572641299051 }, + { 0.680845831422, 0.15781622647, 0.567366404276 }, + { 0.700653597713, 0.146405230587, 0.562091509501 }, + { 0.720461364003, 0.134994234703, 0.556816614726 }, + { 0.740269130293, 0.12358323882, 0.551541719951 }, + { 0.760076896583, 0.112172242937, 0.546266825176 }, + { 0.779884662874, 0.100761247054, 0.540991930401 }, + { 0.799692429164, 0.0893502511705, 0.535717035625 }, + { 0.819500195454, 0.0779392552872, 0.53044214085 }, + { 0.839307961744, 0.066528259404, 0.525167246075 }, + { 0.859115728035, 0.0551172635208, 0.5198923513 }, + { 0.878923494325, 0.0437062676375, 0.514617456525 }, + { 0.898731260615, 0.0322952717543, 0.50934256175 }, + { 0.918539026906, 0.020884275871, 0.504067666975 }, + { 0.938346793196, 0.00947327998777, 0.4987927722 }, + { 0.936655136417, 0.0160553639755, 0.488442906737 }, + { 0.93138024246, 0.0256362946083, 0.477247212827 }, + { 0.926105348503, 0.0352172252412, 0.466051518917 }, + { 0.920830454546, 0.044798155874, 0.454855825007 }, + { 0.915555560589, 0.0543790865069, 0.443660131097 }, + { 0.910280666632, 0.0639600171397, 0.432464437187 }, + { 0.905005772675, 0.0735409477726, 0.421268743277 }, + { 0.899730878718, 0.0831218784054, 0.410073049366 }, + { 0.894455984761, 0.0927028090383, 0.398877355456 }, + { 0.889181090804, 0.102283739671, 0.387681661546 }, + { 0.883906196847, 0.111864670304, 0.376485967636 }, + { 0.87863130289, 0.121445600937, 0.365290273726 }, + { 0.873356408933, 0.13102653157, 0.354094579816 }, + { 0.868081514976, 0.140607462203, 0.342898885906 }, + { 0.862806621019, 0.150188392835, 0.331703191996 }, + { 0.857531727062, 0.159769323468, 0.320507498085 }, + { 0.852256833105, 0.169350254101, 0.309311804175 }, + { 0.846981939148, 0.178931184734, 0.298116110265 }, + { 0.84170704519, 0.188512115367, 0.286920416355 }, + { 0.836432151233, 0.198093046, 0.275724722445 }, + { 0.831157257276, 0.207673976632, 0.264529028535 }, + { 0.825882363319, 0.217254907265, 0.253333334625 }, + { 0.820607469362, 0.226835837898, 0.242137640715 }, + { 0.815332575405, 0.236416768531, 0.230941946805 }, + { 0.810057681448, 0.245997699164, 0.219746252894 }, + { 0.804782787491, 0.255578629797, 0.208550558984 }, + { 0.799507893534, 0.26515956043, 0.197354865074 }, + { 0.794232999577, 0.274740491062, 0.186159171164 }, + { 0.78895810562, 0.284321421695, 0.174963477254 }, + { 0.783683211663, 0.293902352328, 0.163767783344 }, + { 0.778408317706, 0.303483282961, 0.152572089434 }, + { 0.773133423749, 0.313064213594, 0.141376395524 }, + { 0.767858529792, 0.322645144227, 0.130180701613 }, + { 0.762583635835, 0.33222607486, 0.118985007703 }, + { 0.757308741878, 0.341807005492, 0.107789313793 }, + { 0.752033847921, 0.351387936125, 0.0965936198831 }, + { 0.744913509663, 0.357370250716, 0.09384083257 }, + { 0.735332579005, 0.358554410584, 0.102345254053 }, + { 0.725751648347, 0.359738570452, 0.110849675536 }, + { 0.716170717688, 0.36092273032, 0.119354097019 }, + { 0.70658978703, 0.362106890188, 0.127858518502 }, + { 0.697008856371, 0.363291050055, 0.136362939985 }, + { 0.687427925713, 0.364475209923, 0.144867361468 }, + { 0.677846995055, 0.365659369791, 0.153371782951 }, + { 0.668266064396, 0.366843529659, 0.161876204435 }, + { 0.658685133738, 0.368027689527, 0.170380625918 }, + { 0.649104203079, 0.369211849395, 0.178885047401 }, + { 0.639523272421, 0.370396009263, 0.187389468884 }, + { 0.629942341762, 0.371580169131, 0.195893890367 }, + { 0.620361411104, 0.372764328999, 0.20439831185 }, + { 0.610780480446, 0.373948488867, 0.212902733333 }, + { 0.601199549787, 0.375132648734, 0.221407154816 }, + { 0.591618619129, 0.376316808602, 0.229911576299 }, + { 0.58203768847, 0.37750096847, 0.238415997782 }, + { 0.572456757812, 0.378685128338, 0.246920419265 }, + { 0.562875827154, 0.379869288206, 0.255424840748 }, + { 0.553294896495, 0.381053448074, 0.263929262231 }, + { 0.543713965837, 0.382237607942, 0.272433683714 }, + { 0.534133035178, 0.38342176781, 0.280938105198 }, + { 0.52455210452, 0.384605927678, 0.289442526681 }, + { 0.514971173861, 0.385790087546, 0.297946948164 }, + { 0.505390243203, 0.386974247414, 0.306451369647 }, + { 0.495809312545, 0.388158407281, 0.31495579113 }, + { 0.486228381886, 0.389342567149, 0.323460212613 }, + { 0.476647451228, 0.390526727017, 0.331964634096 }, + { 0.467066520569, 0.391710886885, 0.340469055579 }, + { 0.457485589911, 0.392895046753, 0.348973477062 }, + { 0.447904659253, 0.394079206621, 0.357477898545 }, + { 0.438323728594, 0.395263366489, 0.365982320028 }, + { 0.428742797936, 0.396447526357, 0.374486741511 }, + { 0.419161867277, 0.397631686225, 0.382991162994 }, + { 0.409580936619, 0.398815846093, 0.391495584477 }, + }; + + for (unsigned idx = 0; idx < 256; idx++) + { + mpDiscreteColorTransferFunction->AddRGBPoint(double(idx) / 255.0, + accent_colors[idx][0], + accent_colors[idx][1], + accent_colors[idx][2]); + } + + mpScaleBar->SetOrientationToHorizontal(); + mpScaleBar->GetPositionCoordinate()->SetCoordinateSystemToNormalizedViewport(); + mpScaleBar->GetPositionCoordinate()->SetValue(0.25, 0.84); + mpScaleBar->SetWidth(0.5); + mpScaleBar->SetHeight(0.1); + mpScaleBar->GetTitleTextProperty()->ItalicOff(); + mpScaleBar->GetLabelTextProperty()->ItalicOff(); + mpScaleBar->GetTitleTextProperty()->BoldOff(); + mpScaleBar->GetLabelTextProperty()->BoldOff(); + mpScaleBar->SetLabelFormat("%.2g"); + mpScaleBar->GetTitleTextProperty()->SetFontSize(5.0); + mpScaleBar->GetLabelTextProperty()->SetFontSize(5.0); + mpScaleBar->GetTitleTextProperty()->SetColor(0.0, 0.0, 0.0); + mpScaleBar->GetLabelTextProperty()->SetColor(0.0, 0.0, 0.0); +} + +template +AbstractPyChasteActorGenerator::~AbstractPyChasteActorGenerator() +{ +} + +template +vtkSmartPointer AbstractPyChasteActorGenerator::GetColorTransferFunction() +{ + return mpColorTransferFunction; +} + +template +vtkSmartPointer AbstractPyChasteActorGenerator::GetScaleBar() +{ + return mpScaleBar; +} + +template +vtkSmartPointer AbstractPyChasteActorGenerator::GetDiscreteColorTransferFunction() +{ + return mpDiscreteColorTransferFunction; +} + +template +void AbstractPyChasteActorGenerator::SetShowEdges(bool show) +{ + mShowEdges = show; +} + +template +void AbstractPyChasteActorGenerator::SetShowPoints(bool show) +{ + mShowPoints = show; +} + +template +void AbstractPyChasteActorGenerator::SetPointSize(double size) +{ + mPointSize = size; +} + +template +void AbstractPyChasteActorGenerator::SetEdgeSize(double size) +{ + mEdgeSize = size; +} + +template +void AbstractPyChasteActorGenerator::SetShowScaleBar(double show) +{ + mShowScaleBar = show; +} + +template +void AbstractPyChasteActorGenerator::SetShowVolume(bool show) +{ + mShowVolume = show; +} + +template +void AbstractPyChasteActorGenerator::SetEdgeColor(const c_vector& rColor) +{ + mEdgeColor = rColor; +} + +template +void AbstractPyChasteActorGenerator::SetPointColor(const c_vector& rColor) +{ + mPointColor = rColor; +} + +template +void AbstractPyChasteActorGenerator::SetVolumeColor(const c_vector& rColor) +{ + mVolumeColor = rColor; +} + +template +void AbstractPyChasteActorGenerator::SetVolumeOpacity(double opacity) +{ + mVolumeOpacity = opacity; +} + +template +void AbstractPyChasteActorGenerator::SetDataLabel(const std::string& rLabel) +{ + mDataLabel = rLabel; +} + +template class AbstractPyChasteActorGenerator<2>; +template class AbstractPyChasteActorGenerator<3>; diff --git a/pychaste/src/cpp/visualization/AbstractPyChasteActorGenerator.hpp b/pychaste/src/cpp/visualization/AbstractPyChasteActorGenerator.hpp new file mode 100644 index 0000000000..2cbc6df703 --- /dev/null +++ b/pychaste/src/cpp/visualization/AbstractPyChasteActorGenerator.hpp @@ -0,0 +1,225 @@ +/* + +Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef ABSTRACTPYCHASTEACTORGENERATOR_HPP_ +#define ABSTRACTPYCHASTEACTORGENERATOR_HPP_ + +#include +#include +#include +#include +#include +#include +#include "SmartPointers.hpp" +#include "UblasVectorInclude.hpp" + +/** + * This class generates VTK actors for geometric features. + */ +template +class AbstractPyChasteActorGenerator +{ + +protected: + /** + * The color lookup for continuous entities + */ + vtkSmartPointer mpColorTransferFunction; + + /** + * The color lookup for discrete entities + */ + vtkSmartPointer mpDiscreteColorTransferFunction; + + /** + * Show the edges, using a tube filter + */ + bool mShowEdges; + + /** + * Show the points, using a glyph filter + */ + bool mShowPoints; + + /** + * Show the volume + */ + bool mShowVolume; + + /** + * The edge color in RGB + */ + c_vector mEdgeColor; + + /** + * The point color in RGB + */ + c_vector mPointColor; + + /** + * The volume color in RGB + */ + c_vector mVolumeColor; + + /** + * The volume opacity + */ + double mVolumeOpacity; + + /** + * The default size for points + */ + double mPointSize; + + /** + * The default size for edges + */ + double mEdgeSize; + + /** + * The label for contouring on data + */ + std::string mDataLabel; + + /** + * The scale bar + */ + vtkSmartPointer mpScaleBar; + + /** + * Show the scale bar + */ + bool mShowScaleBar; + +public: + /** + * Constructor + */ + AbstractPyChasteActorGenerator(); + + /** + * Destructor + */ + virtual ~AbstractPyChasteActorGenerator(); + + /** + * @return return the color transfer function + */ + vtkSmartPointer GetColorTransferFunction(); + + /** + * @return return the discrete color transfer function + */ + vtkSmartPointer GetDiscreteColorTransferFunction(); + + /** + * @return return the scale bar + */ + vtkSmartPointer GetScaleBar(); + + /** + * Add the Abstract actor to the renderer + * @param pRenderer the current renderer + */ + virtual void AddActor(vtkSmartPointer pRenderer) = 0; + + /** + * Set whether to show the edges + * @param show whether to show the edges + */ + void SetShowEdges(bool show); + + /** + * Set whether to show the points + * @param show whether to show the points + */ + void SetShowPoints(bool show); + + /** + * Set whether to show the volume + * @param show whether to show the volumes + */ + void SetShowVolume(bool show); + + /** + * Set the edge color in RGB (e.g. (255,255,255) is white) + * @param rColor the edge color + */ + void SetEdgeColor(const c_vector& rColor); + + /** + * Set the point color in RGB (e.g. (255,255,255) is white) + * @param rColor the point color + */ + void SetPointColor(const c_vector& rColor); + + /** + * Set the volume color in RGB (e.g. (255,255,255) is white) + * @param rColor the volume color + */ + void SetVolumeColor(const c_vector& rColor); + + /** + * Set the opacity for the volume + * @param opacity the opacity for the volume + */ + void SetVolumeOpacity(double opacity); + + /** + * Set the default point size + * @param size the default point size + */ + void SetPointSize(double size); + + /** + * Set the default edge size + * @param size the default edge size + */ + void SetEdgeSize(double size); + + /** + * Set the label for contouring data + * @param rLabel the label for contouring data + */ + void SetDataLabel(const std::string& rLabel); + + /** + * Set show scale bar + * @param show show scale bar + */ + void SetShowScaleBar(double show); +}; + +#endif // ABSTRACTPYCHASTEACTORGENERATOR_HPP_ diff --git a/pychaste/src/cpp/visualization/CellPopulationPyChasteActorGenerator.cpp b/pychaste/src/cpp/visualization/CellPopulationPyChasteActorGenerator.cpp new file mode 100644 index 0000000000..297c598d7d --- /dev/null +++ b/pychaste/src/cpp/visualization/CellPopulationPyChasteActorGenerator.cpp @@ -0,0 +1,1285 @@ +/* + +Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "CellPopulationPyChasteActorGenerator.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "CaBasedCellPopulation.hpp" +#include "CellLabel.hpp" +#include "Debug.hpp" +#include "Exception.hpp" +#include "ImmersedBoundaryCellPopulation.hpp" +#include "MeshBasedCellPopulationWithGhostNodes.hpp" +#include "NodeBasedCellPopulation.hpp" +#include "PottsBasedCellPopulation.hpp" +#include "UblasIncludes.hpp" +#include "UblasVectorInclude.hpp" +#include "VertexBasedCellPopulation.hpp" + +template +CellPopulationPyChasteActorGenerator::CellPopulationPyChasteActorGenerator() + : AbstractPyChasteActorGenerator(), + mpCellPopulation(), + mShowMutableMeshEdges(false), + mShowVoronoiMeshEdges(true), + mShowPottsMeshEdges(false), + mShowPottsMeshOutlines(false), + mColorByCellType(false), + mColorByCellData(false), + mColorByCellMutationState(false), + mColorByCellLabel(false), + mShowCellCentres(false), + mColorCellByUserDefined(false) +{ +} + +template +CellPopulationPyChasteActorGenerator::~CellPopulationPyChasteActorGenerator() +{ +} + +template +void CellPopulationPyChasteActorGenerator::AddCaBasedCellPopulationActor(vtkSmartPointer pRenderer) +{ + auto p_potts_grid = vtkSmartPointer::New(); + auto p_geom_filter = vtkSmartPointer::New(); + + boost::shared_ptr > p_ca_population = boost::dynamic_pointer_cast >(mpCellPopulation); + + if (p_ca_population && mShowPottsMeshEdges) + { + auto p_points = vtkSmartPointer::New(); + p_points->GetData()->SetName("Vertex positions"); + + unsigned counter = 0; + c_vector old_loc = zero_vector(DIM); + double spacing = 1.0; + for (typename PottsMesh::NodeIterator node_iter = p_ca_population->rGetMesh().GetNodeIteratorBegin(); + node_iter != p_ca_population->rGetMesh().GetNodeIteratorEnd(); ++node_iter) + { + c_vector current_item = node_iter->rGetLocation(); + if (DIM == 3) + { + p_points->InsertNextPoint(current_item[0], current_item[1], current_item[2]); + } + else if (DIM == 2) + { + p_points->InsertNextPoint(current_item[0], current_item[1], 0.0); + } + else // (DIM == 1) + { + p_points->InsertNextPoint(current_item[0], 0.0, 0.0); + } + + if (counter == 0) + { + old_loc = current_item; + } + if (counter == 1) + { + spacing = norm_2(current_item - old_loc); + } + counter++; + } + + auto p_temp_polydata = vtkSmartPointer::New(); + p_temp_polydata->SetPoints(p_points); + + double bounds[6]; + p_temp_polydata->GetBounds(bounds); + auto p_ca_image = vtkSmartPointer::New(); + p_ca_image->SetDimensions(std::floor((bounds[1] - bounds[0]) / spacing) + 1, + std::floor((bounds[3] - bounds[2]) / spacing) + 1, + std::floor((bounds[5] - bounds[4]) / spacing) + 1); + p_ca_image->SetOrigin(bounds[0], bounds[2], bounds[4]); + p_ca_image->SetSpacing(spacing, spacing, spacing); + + p_geom_filter->SetInputData(p_ca_image); + + auto p_mapper = vtkSmartPointer::New(); + p_mapper->SetInputConnection(p_geom_filter->GetOutputPort()); + + auto p_volume_actor = vtkSmartPointer::New(); + p_volume_actor->SetMapper(p_mapper); + p_volume_actor->GetProperty()->SetEdgeVisibility(this->mShowEdges); + p_volume_actor->GetProperty()->SetLineWidth(this->mEdgeSize); + p_volume_actor->GetProperty()->SetOpacity(0.6); + pRenderer->AddActor(p_volume_actor); + } +} + +template +void CellPopulationPyChasteActorGenerator::AddPottsBasedCellPopulationActor(vtkSmartPointer pRenderer) +{ + auto p_potts_grid = vtkSmartPointer::New(); + + boost::shared_ptr > p_potts_population = boost::dynamic_pointer_cast >(mpCellPopulation); + + if (p_potts_population && mShowPottsMeshEdges) + { + auto p_points = vtkSmartPointer::New(); + p_points->GetData()->SetName("Vertex positions"); + + unsigned counter = 0; + c_vector old_loc = zero_vector(DIM); + double spacing = 1.0; + + for (typename PottsMesh::NodeIterator node_iter = p_potts_population->rGetMesh().GetNodeIteratorBegin(); + node_iter != p_potts_population->rGetMesh().GetNodeIteratorEnd(); + ++node_iter) + { + c_vector current_item = node_iter->rGetLocation(); + if (DIM == 3) + { + p_points->InsertNextPoint(current_item[0], current_item[1], current_item[2]); + } + else if (DIM == 2) + { + p_points->InsertNextPoint(current_item[0], current_item[1], 0.0); + } + else // (DIM == 1) + { + p_points->InsertNextPoint(current_item[0], 0.0, 0.0); + } + if (counter == 0) + { + old_loc = current_item; + } + if (counter == 1) + { + spacing = norm_2(current_item - old_loc); + } + counter++; + } + + auto p_temp_polydata = vtkSmartPointer::New(); + p_temp_polydata->SetPoints(p_points); + + double bounds[6]; + p_temp_polydata->GetBounds(bounds); + p_potts_grid = vtkSmartPointer::New(); + + // Important: We color VTK cells, not points. We add a VTK cell for each Chaste node. + p_potts_grid->SetDimensions(std::floor((bounds[1] - bounds[0]) / spacing) + 2, + std::floor((bounds[3] - bounds[2]) / spacing) + 2, + std::floor((bounds[5] - bounds[4]) / spacing) + 2); + p_potts_grid->SetOrigin(bounds[0] - spacing / 2.0, bounds[2] - spacing / 2.0, bounds[4] - spacing / 2.0); + p_potts_grid->SetSpacing(spacing, spacing, spacing); + + auto p_element_ids = vtkSmartPointer::New(); + p_element_ids->SetNumberOfTuples(p_potts_grid->GetNumberOfPoints()); + p_element_ids->SetName("Cell Id"); + + auto p_element_base_ids = vtkSmartPointer::New(); + p_element_base_ids->SetNumberOfTuples(p_potts_grid->GetNumberOfPoints()); + p_element_base_ids->SetName("Cell Base Id"); + for (unsigned idx = 0; idx < p_potts_grid->GetNumberOfPoints(); idx++) + { + p_element_ids->SetTuple1(idx, -1.0); + } + + for (typename AbstractCellPopulation::Iterator cell_iter = mpCellPopulation->Begin(); + cell_iter != mpCellPopulation->End(); ++cell_iter) + { + PottsElement* p_element = p_potts_population->GetElementCorrespondingToCell(*cell_iter); + + for (unsigned idx = 0; idx < p_element->GetNumNodes(); idx++) + { + unsigned node_index = p_element->GetNode(idx)->GetIndex(); + + if (mColorByCellType) + { + p_element_ids->InsertNextTuple1((*cell_iter)->GetCellProliferativeType()->GetColour()); + } + else if (mColorByCellData && !this->mDataLabel.empty()) + { + std::vector keys = (*cell_iter)->GetCellData()->GetKeys(); + if (std::find(keys.begin(), keys.end(), this->mDataLabel) != keys.end()) + { + p_element_ids->InsertNextTuple1((*cell_iter)->GetCellData()->GetItem(this->mDataLabel)); + } + else + { + p_element_ids->InsertNextTuple1(0.0); + } + } + + else if (mColorByCellMutationState) + { + double mutation_state = (*cell_iter)->GetMutationState()->GetColour(); + CellPropertyCollection collection = (*cell_iter)->rGetCellPropertyCollection(); + CellPropertyCollection label_collection = collection.GetProperties(); + + if (label_collection.GetSize() == 1) + { + boost::shared_ptr p_label = boost::static_pointer_cast(label_collection.GetProperty()); + mutation_state = p_label->GetColour(); + } + p_element_ids->InsertNextTuple1(mutation_state); + } + else if (mColorByCellLabel) + { + double label = 0.0; + if ((*cell_iter)->template HasCellProperty()) + { + CellPropertyCollection collection = (*cell_iter)->rGetCellPropertyCollection().template GetProperties(); + boost::shared_ptr p_label = boost::static_pointer_cast(collection.GetProperty()); + label = p_label->GetColour(); + } + p_element_ids->InsertNextTuple1(label); + } + else + { + p_element_ids->SetTuple1(node_index, double((*cell_iter)->GetCellId() + 1)); + } + p_element_base_ids->SetTuple1(node_index, double((*cell_iter)->GetCellId() + 1)); + } + } + p_potts_grid->GetCellData()->SetScalars(p_element_ids); + p_potts_grid->GetCellData()->AddArray(p_element_ids); + p_potts_grid->GetCellData()->AddArray(p_element_base_ids); + + auto p_scaled_ctf = vtkSmartPointer::New(); + if (!mColorByCellData) + { + double range[2]; + p_potts_grid->GetCellData()->GetArray("Cell Id")->GetRange(range); + for (unsigned idx = 0; idx < 255; idx++) + { + double color[3]; + this->mpDiscreteColorTransferFunction->GetColor(double(idx) / 255.0, color); + p_scaled_ctf->AddRGBPoint(double(idx) * range[1] / 255.0, color[0], color[1], color[2]); + } + } + + auto p_geometry_filter_pre = vtkSmartPointer::New(); + p_geometry_filter_pre->SetInputData(p_potts_grid); + + auto p_threshold = vtkSmartPointer::New(); + p_threshold->SetInputConnection(p_geometry_filter_pre->GetOutputPort()); + +#if (VTK_MAJOR_VERSION < 9 || (VTK_MAJOR_VERSION == 9 && VTK_MINOR_VERSION < 1)) // VTK < 9.1 + p_threshold->ThresholdByUpper(0.0); +#else + p_threshold->SetUpperThreshold(0.0); + p_threshold->SetThresholdFunction(vtkThreshold::THRESHOLD_UPPER); +#endif + p_threshold->SetInputArrayToProcess(0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_CELLS, "Cell Id"); + + auto p_geom_filter = vtkSmartPointer::New(); + p_geom_filter->SetInputConnection(p_threshold->GetOutputPort()); + + auto p_mapper = vtkSmartPointer::New(); + p_mapper->SetInputConnection(p_geom_filter->GetOutputPort()); + p_mapper->SetLookupTable(p_scaled_ctf); + p_mapper->ScalarVisibilityOn(); + p_mapper->SelectColorArray("Cell Id"); + p_mapper->SetScalarModeToUseCellData(); + p_mapper->SetColorModeToMapScalars(); + + auto p_volume_actor = vtkSmartPointer::New(); + p_volume_actor->SetMapper(p_mapper); + // p_volume_actor->GetProperty()->SetEdgeVisibility(this->mShowEdges); + p_volume_actor->GetProperty()->SetLineWidth(this->mEdgeSize); + p_volume_actor->GetProperty()->SetOpacity(this->mVolumeOpacity); + if (mColorCellByUserDefined) + { + p_volume_actor->GetProperty()->SetColor(this->mPointColor[0], this->mPointColor[1], this->mPointColor[2]); + } + pRenderer->AddActor(p_volume_actor); + + if (mShowPottsMeshOutlines) + { + auto p_bounds = vtkSmartPointer::New(); + + for (unsigned idx = 0; idx < p_potts_grid->GetNumberOfCells(); idx++) + { + auto p_local_threshold = vtkSmartPointer::New(); + p_local_threshold->SetInputData(p_geom_filter->GetOutput()); + +#if (VTK_MAJOR_VERSION < 9 || (VTK_MAJOR_VERSION == 9 && VTK_MINOR_VERSION < 1)) // VTK < 9.1 + p_local_threshold->ThresholdBetween(p_element_base_ids->GetTuple1(idx), + p_element_base_ids->GetTuple1(idx)); +#else + p_local_threshold->SetLowerThreshold(p_element_base_ids->GetTuple1(idx)); + p_local_threshold->SetUpperThreshold(p_element_base_ids->GetTuple1(idx)); + p_local_threshold->SetThresholdFunction(vtkThreshold::THRESHOLD_BETWEEN); +#endif + + p_local_threshold->SetInputArrayToProcess(0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_CELLS, "Cell Base Id"); + + auto p_local_geom_filter = vtkSmartPointer::New(); + p_local_geom_filter->SetInputConnection(p_local_threshold->GetOutputPort()); + + auto p_features = vtkSmartPointer::New(); + p_features->SetInputConnection(p_local_geom_filter->GetOutputPort()); + p_features->Update(); + + auto p_append = vtkSmartPointer::New(); + p_append->AddInputData(p_bounds); + p_append->AddInputData(p_features->GetOutput()); + p_append->Update(); + p_bounds = p_append->GetOutput(); + } + + auto p_mapper2 = vtkSmartPointer::New(); + p_mapper2->SetInputData(p_bounds); + + auto p_volume_actor2 = vtkSmartPointer::New(); + p_volume_actor2->SetMapper(p_mapper2); + p_volume_actor2->GetProperty()->SetEdgeVisibility(this->mShowEdges); + p_volume_actor2->GetProperty()->SetLineWidth(this->mEdgeSize); + p_volume_actor2->GetProperty()->SetColor(this->mEdgeColor[0], this->mEdgeColor[1], this->mEdgeColor[2]); + p_volume_actor2->GetProperty()->SetOpacity(this->mVolumeOpacity); + pRenderer->AddActor(p_volume_actor2); + } + } +} + +template +void CellPopulationPyChasteActorGenerator::AddActor(vtkSmartPointer pRenderer) +{ + if (!mpCellPopulation) + { + return; + } + + // Show cell centres if requested + if (mShowCellCentres || boost::dynamic_pointer_cast >(mpCellPopulation) || boost::dynamic_pointer_cast >(mpCellPopulation)) + { + auto p_points = vtkSmartPointer::New(); + auto p_cell_color_reference_data = vtkSmartPointer::New(); + p_cell_color_reference_data->SetName("CellColors"); + auto p_polydata = vtkSmartPointer::New(); + + for (typename AbstractCellPopulation::Iterator cell_iter = mpCellPopulation->Begin(); + cell_iter != mpCellPopulation->End(); ++cell_iter) + { + c_vector centre = mpCellPopulation->GetLocationOfCellCentre(*cell_iter); + if (DIM == 3) + { + p_points->InsertNextPoint(centre[0], centre[1], centre[2]); + } + else + { + p_points->InsertNextPoint(centre[0], centre[1], 0.0); + } + + if (mColorByCellType) + { + p_cell_color_reference_data->InsertNextTuple1((*cell_iter)->GetCellProliferativeType()->GetColour()); + } + else if (mColorByCellData && !this->mDataLabel.empty()) + { + std::vector keys = (*cell_iter)->GetCellData()->GetKeys(); + if (std::find(keys.begin(), keys.end(), this->mDataLabel) != keys.end()) + { + p_cell_color_reference_data->InsertNextTuple1((*cell_iter)->GetCellData()->GetItem(this->mDataLabel)); + } + else + { + p_cell_color_reference_data->InsertNextTuple1(0.0); + } + } + else if (mColorByCellMutationState) + { + double mutation_state = (*cell_iter)->GetMutationState()->GetColour(); + + CellPropertyCollection collection = (*cell_iter)->rGetCellPropertyCollection(); + CellPropertyCollection label_collection = collection.GetProperties(); + + if (label_collection.GetSize() == 1) + { + boost::shared_ptr p_label = boost::static_pointer_cast(label_collection.GetProperty()); + mutation_state = p_label->GetColour(); + } + p_cell_color_reference_data->InsertNextTuple1(mutation_state); + } + else if (mColorByCellLabel) + { + double label = 0.0; + if ((*cell_iter)->template HasCellProperty()) + { + CellPropertyCollection collection = (*cell_iter)->rGetCellPropertyCollection().template GetProperties(); + boost::shared_ptr p_label = boost::static_pointer_cast(collection.GetProperty()); + label = p_label->GetColour(); + } + p_cell_color_reference_data->InsertNextTuple1(label); + } + else + { + p_cell_color_reference_data->InsertNextTuple1((*cell_iter)->GetCellId()); + } + } + p_polydata->SetPoints(p_points); + p_polydata->GetPointData()->AddArray(p_cell_color_reference_data); + + auto p_scaled_ctf = vtkSmartPointer::New(); + if (!mColorByCellData) + { + double range[2]; + p_polydata->GetPointData()->GetArray("CellColors")->GetRange(range); + for (unsigned idx = 0; idx < 255; idx++) + { + double color[3]; + this->mpDiscreteColorTransferFunction->GetColor((255.0 - double(idx)) / 255.0, color); + p_scaled_ctf->AddRGBPoint(range[0] + double(idx) * (range[1] - range[0]) / 255.0, color[0], color[1], color[2]); + } + } + else + { + double range[2]; + p_polydata->GetPointData()->GetArray("CellColors")->GetRange(range); + for (unsigned idx = 0; idx < 255; idx++) + { + double color[3]; + this->mpColorTransferFunction->GetColor(double(idx) / 255.0, color); + p_scaled_ctf->AddRGBPoint(range[0] + double(idx) * (range[1] - range[0]) / 255.0, color[0], color[1], color[2]); + } + } + + auto p_spheres = vtkSmartPointer::New(); + p_spheres->SetRadius(this->mPointSize); + p_spheres->SetPhiResolution(16); + p_spheres->SetThetaResolution(16); + + auto p_glyph = vtkSmartPointer::New(); + p_glyph->SetInputData(p_polydata); + p_glyph->SetSourceConnection(p_spheres->GetOutputPort()); + p_glyph->ClampingOff(); + p_glyph->SetScaleModeToScaleByScalar(); + p_glyph->SetScaleFactor(1.0); + p_glyph->Update(); + + auto p_mapper = vtkSmartPointer::New(); + p_mapper->SetInputData(p_glyph->GetOutput()); + p_mapper->SetLookupTable(p_scaled_ctf); + p_mapper->ScalarVisibilityOn(); + p_mapper->SelectColorArray("CellColors"); + p_mapper->SetScalarModeToUsePointFieldData(); + p_mapper->SetColorModeToMapScalars(); + + auto p_actor = vtkSmartPointer::New(); + p_actor->SetMapper(p_mapper); + p_actor->GetProperty()->SetOpacity(this->mVolumeOpacity); + if (mColorCellByUserDefined) + { + p_actor->GetProperty()->SetColor(this->mPointColor[0], this->mPointColor[1], this->mPointColor[2]); + } + pRenderer->AddActor(p_actor); + + if (!this->mDataLabel.empty() && this->mShowScaleBar) + { + this->mpScaleBar->SetLookupTable(p_scaled_ctf); + this->mpScaleBar->SetTitle(this->mDataLabel.c_str()); + pRenderer->AddActor(this->mpScaleBar); + } + } + + if (boost::dynamic_pointer_cast >(mpCellPopulation) && (mShowMutableMeshEdges || mShowVoronoiMeshEdges)) + { + AddMeshBasedCellPopulationActor(pRenderer); + } + else if (boost::dynamic_pointer_cast >(mpCellPopulation) && mShowVoronoiMeshEdges) + { + AddVertexBasedCellPopulationActor(pRenderer); + } + else if (boost::dynamic_pointer_cast >(mpCellPopulation) && (mShowPottsMeshEdges || mShowPottsMeshOutlines)) + { + AddPottsBasedCellPopulationActor(pRenderer); + } + else if (boost::dynamic_pointer_cast >(mpCellPopulation) && mShowPottsMeshEdges) + { + AddCaBasedCellPopulationActor(pRenderer); + } + else if (boost::dynamic_pointer_cast >(mpCellPopulation)) + { + AddImmersedBoundaryCellPopulationActor(pRenderer); + } +} + +template +void CellPopulationPyChasteActorGenerator::AddVertexBasedCellPopulationActor(vtkSmartPointer pRenderer) +{ + boost::shared_ptr > p_cell_population = boost::dynamic_pointer_cast >(mpCellPopulation); + + if (!p_cell_population) + { + EXCEPTION("Could not cast mesh to Vertex Based type."); + } + + if (mShowVoronoiMeshEdges) + { + auto p_voronoi_grid = vtkSmartPointer::New(); + auto p_cell_color_reference_data = vtkSmartPointer::New(); + p_cell_color_reference_data->SetName("CellColors"); + + auto p_points = vtkSmartPointer::New(); + p_points->GetData()->SetName("Vertex positions"); + for (unsigned node_num = 0; node_num < p_cell_population->rGetMesh().GetNumNodes(); node_num++) + { + c_vector position = p_cell_population->rGetMesh().GetNode(node_num)->rGetLocation(); + if (DIM == 2) + { + p_points->InsertPoint(node_num, position[0], position[1], 0.0); + } + else + { + p_points->InsertPoint(node_num, position[0], position[1], position[2]); + } + } + p_voronoi_grid->SetPoints(p_points); + + for (typename VertexMesh::VertexElementIterator iter = p_cell_population->rGetMesh().GetElementIteratorBegin(); + iter != p_cell_population->rGetMesh().GetElementIteratorEnd(); ++iter) + { + vtkSmartPointer p_cell; + if (DIM == 2) + { + p_cell = vtkSmartPointer::New(); + } + else + { + p_cell = vtkSmartPointer::New(); + } + vtkSmartPointer p_cell_id_list = p_cell->GetPointIds(); + p_cell_id_list->SetNumberOfIds(iter->GetNumNodes()); + for (unsigned j = 0; j < iter->GetNumNodes(); ++j) + { + p_cell_id_list->SetId(j, iter->GetNodeGlobalIndex(j)); + } + p_voronoi_grid->InsertNextCell(p_cell->GetCellType(), p_cell_id_list); + + unsigned element_index = iter->GetIndex(); + CellPtr p_biological_cell = p_cell_population->GetCellUsingLocationIndex(element_index); + + if (mColorByCellType) + { + p_cell_color_reference_data->InsertNextTuple1(p_biological_cell->GetCellProliferativeType()->GetColour()); + } + + else if (mColorByCellData && !this->mDataLabel.empty()) + { + std::vector keys = p_biological_cell->GetCellData()->GetKeys(); + if (std::find(keys.begin(), keys.end(), this->mDataLabel) != keys.end()) + { + p_cell_color_reference_data->InsertNextTuple1(p_biological_cell->GetCellData()->GetItem(this->mDataLabel)); + } + else + { + p_cell_color_reference_data->InsertNextTuple1(0.0); + } + } + + else if (mColorByCellMutationState) + { + double mutation_state = p_biological_cell->GetMutationState()->GetColour(); + + CellPropertyCollection collection = p_biological_cell->rGetCellPropertyCollection(); + CellPropertyCollection label_collection = collection.GetProperties(); + + if (label_collection.GetSize() == 1) + { + boost::shared_ptr p_label = boost::static_pointer_cast(label_collection.GetProperty()); + mutation_state = p_label->GetColour(); + } + p_cell_color_reference_data->InsertNextTuple1(mutation_state); + } + else if (mColorByCellLabel) + { + double label = 0.0; + if (p_biological_cell->HasCellProperty()) + { + CellPropertyCollection collection = p_biological_cell->rGetCellPropertyCollection().GetProperties(); + boost::shared_ptr p_label = boost::static_pointer_cast(collection.GetProperty()); + label = p_label->GetColour(); + } + p_cell_color_reference_data->InsertNextTuple1(label); + } + else + { + p_cell_color_reference_data->InsertNextTuple1(p_biological_cell->GetCellId()); + } + } + + p_voronoi_grid->GetCellData()->AddArray(p_cell_color_reference_data); + p_voronoi_grid->GetCellData()->SetScalars(p_cell_color_reference_data); + + auto p_scaled_ctf = vtkSmartPointer::New(); + if (!mColorByCellData) + { + double range[2]; + p_voronoi_grid->GetCellData()->GetArray("CellColors")->GetRange(range); + for (unsigned idx = 0; idx < 255; idx++) + { + double color[3]; + this->mpDiscreteColorTransferFunction->GetColor((255.0 - double(idx)) / 255.0, color); + p_scaled_ctf->AddRGBPoint(range[0] + double(idx) * (range[1] - range[0]) / 255.0, color[0], color[1], color[2]); + } + } + else + { + double range[2]; + p_voronoi_grid->GetCellData()->GetArray("CellColors")->GetRange(range); + for (unsigned idx = 0; idx < 255; idx++) + { + double color[3]; + this->mpColorTransferFunction->GetColor(double(idx) / 255.0, color); + p_scaled_ctf->AddRGBPoint(range[0] + double(idx) * (range[1] - range[0]) / 255.0, color[0], color[1], color[2]); + } + } + p_scaled_ctf->Build(); + + auto p_geom_filter = vtkSmartPointer::New(); + p_geom_filter->SetInputData(p_voronoi_grid); + + auto p_mapper = vtkSmartPointer::New(); + p_mapper->SetInputConnection(p_geom_filter->GetOutputPort()); + p_mapper->SetLookupTable(p_scaled_ctf); + p_mapper->ScalarVisibilityOn(); + p_mapper->SelectColorArray("CellColors"); + p_mapper->SetScalarModeToUseCellData(); + p_mapper->SetColorModeToMapScalars(); + + auto p_actor = vtkSmartPointer::New(); + p_actor->SetMapper(p_mapper); + p_actor->GetProperty()->SetOpacity(this->mVolumeOpacity); + if (mColorCellByUserDefined) + { + p_actor->GetProperty()->SetColor(this->mPointColor[0], this->mPointColor[1], this->mPointColor[2]); + } + pRenderer->AddActor(p_actor); + + auto p_voronoi_extract_edges = vtkSmartPointer::New(); + p_voronoi_extract_edges->SetInputConnection(p_geom_filter->GetOutputPort()); + p_voronoi_extract_edges->SetFeatureEdges(false); + p_voronoi_extract_edges->SetBoundaryEdges(true); + p_voronoi_extract_edges->SetManifoldEdges(true); + p_voronoi_extract_edges->SetNonManifoldEdges(false); + + auto p_voronoi_tubes = vtkSmartPointer::New(); + p_voronoi_tubes->SetInputConnection(p_voronoi_extract_edges->GetOutputPort()); + p_voronoi_tubes->SetRadius(this->mEdgeSize); + p_voronoi_tubes->SetNumberOfSides(12); + + auto p_voronoi_tube_mapper = vtkSmartPointer::New(); + p_voronoi_tube_mapper->SetInputConnection(p_voronoi_tubes->GetOutputPort()); + p_voronoi_tube_mapper->ScalarVisibilityOff(); + + auto p_voronoi_tube_actor = vtkSmartPointer::New(); + p_voronoi_tube_actor->SetMapper(p_voronoi_tube_mapper); + p_voronoi_tube_actor->GetProperty()->SetColor(this->mEdgeColor[0], this->mEdgeColor[1], this->mEdgeColor[2]); + pRenderer->AddActor(p_voronoi_tube_actor); + + if (!this->mDataLabel.empty() && this->mShowScaleBar) + { + this->mpScaleBar->SetLookupTable(p_scaled_ctf); + this->mpScaleBar->SetTitle(this->mDataLabel.c_str()); + pRenderer->AddActor(this->mpScaleBar); + } + } +} + +template +void CellPopulationPyChasteActorGenerator::AddImmersedBoundaryCellPopulationActor(vtkSmartPointer pRenderer) +{ + boost::shared_ptr > p_cell_population = boost::dynamic_pointer_cast >(mpCellPopulation); + + if (!p_cell_population) + { + EXCEPTION("Could not cast mesh to Immersed Boundary type."); + } + + if (mShowVoronoiMeshEdges) + { + auto p_voronoi_grid = vtkSmartPointer::New(); + auto p_cell_color_reference_data = vtkSmartPointer::New(); + p_cell_color_reference_data->SetName("CellColors"); + + auto p_points = vtkSmartPointer::New(); + p_points->GetData()->SetName("Vertex positions"); + for (unsigned node_num = 0; node_num < p_cell_population->rGetMesh().GetNumNodes(); node_num++) + { + c_vector position = p_cell_population->rGetMesh().GetNode(node_num)->rGetLocation(); + if (DIM == 2) + { + p_points->InsertPoint(node_num, position[0], position[1], 0.0); + } + else + { + p_points->InsertPoint(node_num, position[0], position[1], position[2]); + } + } + p_voronoi_grid->SetPoints(p_points); + + for (typename ImmersedBoundaryMesh::ImmersedBoundaryElementIterator iter = p_cell_population->rGetMesh().GetElementIteratorBegin(); + iter != p_cell_population->rGetMesh().GetElementIteratorEnd(); ++iter) + { + vtkSmartPointer p_cell; + if (DIM == 2) + { + p_cell = vtkSmartPointer::New(); + } + else + { + p_cell = vtkSmartPointer::New(); + } + vtkSmartPointer p_cell_id_list = p_cell->GetPointIds(); + p_cell_id_list->SetNumberOfIds(iter->GetNumNodes()); + for (unsigned j = 0; j < iter->GetNumNodes(); ++j) + { + p_cell_id_list->SetId(j, iter->GetNodeGlobalIndex(j)); + } + p_voronoi_grid->InsertNextCell(p_cell->GetCellType(), p_cell_id_list); + + unsigned element_index = iter->GetIndex(); + CellPtr p_biological_cell = p_cell_population->GetCellUsingLocationIndex(element_index); + + if (mColorByCellType) + { + p_cell_color_reference_data->InsertNextTuple1(p_biological_cell->GetCellProliferativeType()->GetColour()); + } + + else if (mColorByCellData && !this->mDataLabel.empty()) + { + std::vector keys = p_biological_cell->GetCellData()->GetKeys(); + if (std::find(keys.begin(), keys.end(), this->mDataLabel) != keys.end()) + { + p_cell_color_reference_data->InsertNextTuple1(p_biological_cell->GetCellData()->GetItem(this->mDataLabel)); + } + else + { + p_cell_color_reference_data->InsertNextTuple1(0.0); + } + } + + else if (mColorByCellMutationState) + { + double mutation_state = p_biological_cell->GetMutationState()->GetColour(); + + CellPropertyCollection collection = p_biological_cell->rGetCellPropertyCollection(); + CellPropertyCollection label_collection = collection.GetProperties(); + + if (label_collection.GetSize() == 1) + { + boost::shared_ptr p_label = boost::static_pointer_cast(label_collection.GetProperty()); + mutation_state = p_label->GetColour(); + } + p_cell_color_reference_data->InsertNextTuple1(mutation_state); + } + else if (mColorByCellLabel) + { + double label = 0.0; + if (p_biological_cell->HasCellProperty()) + { + CellPropertyCollection collection = p_biological_cell->rGetCellPropertyCollection().GetProperties(); + boost::shared_ptr p_label = boost::static_pointer_cast(collection.GetProperty()); + label = p_label->GetColour(); + } + p_cell_color_reference_data->InsertNextTuple1(label); + } + else + { + p_cell_color_reference_data->InsertNextTuple1(p_biological_cell->GetCellId()); + } + } + + p_voronoi_grid->GetCellData()->AddArray(p_cell_color_reference_data); + p_voronoi_grid->GetCellData()->SetScalars(p_cell_color_reference_data); + + auto p_scaled_ctf = vtkSmartPointer::New(); + if (!mColorByCellData) + { + double range[2]; + p_voronoi_grid->GetCellData()->GetArray("CellColors")->GetRange(range); + for (unsigned idx = 0; idx < 255; idx++) + { + double color[3]; + this->mpDiscreteColorTransferFunction->GetColor((255.0 - double(idx)) / 255.0, color); + p_scaled_ctf->AddRGBPoint(range[0] + double(idx) * (range[1] - range[0]) / 255.0, color[0], color[1], color[2]); + } + } + else + { + double range[2]; + p_voronoi_grid->GetCellData()->GetArray("CellColors")->GetRange(range); + for (unsigned idx = 0; idx < 255; idx++) + { + double color[3]; + this->mpColorTransferFunction->GetColor(double(idx) / 255.0, color); + p_scaled_ctf->AddRGBPoint(range[0] + double(idx) * (range[1] - range[0]) / 255.0, color[0], color[1], color[2]); + } + } + p_scaled_ctf->Build(); + + auto p_geom_filter = vtkSmartPointer::New(); + p_geom_filter->SetInputData(p_voronoi_grid); + + auto p_mapper = vtkSmartPointer::New(); + p_mapper->SetInputConnection(p_geom_filter->GetOutputPort()); + p_mapper->SetLookupTable(p_scaled_ctf); + p_mapper->ScalarVisibilityOn(); + p_mapper->SelectColorArray("CellColors"); + p_mapper->SetScalarModeToUseCellData(); + p_mapper->SetColorModeToMapScalars(); + + auto p_actor = vtkSmartPointer::New(); + p_actor->SetMapper(p_mapper); + p_actor->GetProperty()->SetOpacity(this->mVolumeOpacity); + if (mColorCellByUserDefined) + { + p_actor->GetProperty()->SetColor(this->mPointColor[0], this->mPointColor[1], this->mPointColor[2]); + } + pRenderer->AddActor(p_actor); + + auto p_voronoi_extract_edges = vtkSmartPointer::New(); + p_voronoi_extract_edges->SetInputConnection(p_geom_filter->GetOutputPort()); + p_voronoi_extract_edges->SetFeatureEdges(false); + p_voronoi_extract_edges->SetBoundaryEdges(true); + p_voronoi_extract_edges->SetManifoldEdges(true); + p_voronoi_extract_edges->SetNonManifoldEdges(false); + + auto p_voronoi_tubes = vtkSmartPointer::New(); + p_voronoi_tubes->SetInputConnection(p_voronoi_extract_edges->GetOutputPort()); + p_voronoi_tubes->SetRadius(this->mEdgeSize); + p_voronoi_tubes->SetNumberOfSides(12); + + auto p_voronoi_tube_mapper = vtkSmartPointer::New(); + p_voronoi_tube_mapper->SetInputConnection(p_voronoi_tubes->GetOutputPort()); + p_voronoi_tube_mapper->ScalarVisibilityOff(); + + auto p_voronoi_tube_actor = vtkSmartPointer::New(); + p_voronoi_tube_actor->SetMapper(p_voronoi_tube_mapper); + p_voronoi_tube_actor->GetProperty()->SetColor(this->mEdgeColor[0], this->mEdgeColor[1], this->mEdgeColor[2]); + pRenderer->AddActor(p_voronoi_tube_actor); + + if (!this->mDataLabel.empty() && this->mShowScaleBar) + { + this->mpScaleBar->SetLookupTable(p_scaled_ctf); + this->mpScaleBar->SetTitle(this->mDataLabel.c_str()); + pRenderer->AddActor(this->mpScaleBar); + } + } +} + +template +void CellPopulationPyChasteActorGenerator::AddMeshBasedCellPopulationActor(vtkSmartPointer pRenderer) +{ + boost::shared_ptr > p_cell_population = boost::dynamic_pointer_cast >(mpCellPopulation); + boost::shared_ptr > p_cell_population_with_ghost = boost::dynamic_pointer_cast >(mpCellPopulation); + + if (!p_cell_population) + { + EXCEPTION("Could not cast mesh to MeshBased type."); + } + + // Add the voronoi mesh + if (mShowVoronoiMeshEdges) + { + p_cell_population->CreateVoronoiTessellation(); + auto p_voronoi_grid = vtkSmartPointer::New(); + auto p_cell_color_reference_data = vtkSmartPointer::New(); + p_cell_color_reference_data->SetName("CellColors"); + + if (p_cell_population->GetVoronoiTessellation() != NULL) + { + auto p_points = vtkSmartPointer::New(); + p_points->GetData()->SetName("Vertex positions"); + for (unsigned node_num = 0; node_num < p_cell_population->GetVoronoiTessellation()->GetNumNodes(); node_num++) + { + c_vector position = p_cell_population->GetVoronoiTessellation()->GetNode(node_num)->rGetLocation(); + if (DIM == 2) + { + p_points->InsertPoint(node_num, position[0], position[1], 0.0); + } + else + { + p_points->InsertPoint(node_num, position[0], position[1], position[2]); + } + } + p_voronoi_grid->SetPoints(p_points); + + for (typename VertexMesh::VertexElementIterator iter = p_cell_population->GetVoronoiTessellation()->GetElementIteratorBegin(); + iter != p_cell_population->GetVoronoiTessellation()->GetElementIteratorEnd(); ++iter) + { + vtkSmartPointer p_cell; + if (DIM == 2) + { + p_cell = vtkSmartPointer::New(); + } + else + { + p_cell = vtkSmartPointer::New(); + } + vtkSmartPointer p_cell_id_list = p_cell->GetPointIds(); + p_cell_id_list->SetNumberOfIds(iter->GetNumNodes()); + for (unsigned j = 0; j < iter->GetNumNodes(); ++j) + { + p_cell_id_list->SetId(j, iter->GetNodeGlobalIndex(j)); + } + p_voronoi_grid->InsertNextCell(p_cell->GetCellType(), p_cell_id_list); + + unsigned node_index = p_cell_population->GetVoronoiTessellation()->GetDelaunayNodeIndexCorrespondingToVoronoiElementIndex(iter->GetIndex()); + + bool is_ghost_node = false; + if (p_cell_population_with_ghost) + { + is_ghost_node = p_cell_population_with_ghost->IsGhostNode(node_index); + } + + if (!is_ghost_node) + { + CellPtr p_biological_cell = p_cell_population->GetCellUsingLocationIndex(node_index); + + if (mColorByCellType) + { + p_cell_color_reference_data->InsertNextTuple1(p_biological_cell->GetCellProliferativeType()->GetColour()); + } + + else if (mColorByCellData && !this->mDataLabel.empty()) + { + std::vector keys = p_biological_cell->GetCellData()->GetKeys(); + if (std::find(keys.begin(), keys.end(), this->mDataLabel) != keys.end()) + { + p_cell_color_reference_data->InsertNextTuple1(p_biological_cell->GetCellData()->GetItem(this->mDataLabel)); + } + else + { + p_cell_color_reference_data->InsertNextTuple1(0.0); + } + } + else if (mColorByCellMutationState) + { + double mutation_state = p_biological_cell->GetMutationState()->GetColour(); + + CellPropertyCollection collection = p_biological_cell->rGetCellPropertyCollection(); + CellPropertyCollection label_collection = collection.GetProperties(); + + if (label_collection.GetSize() == 1) + { + boost::shared_ptr p_label = boost::static_pointer_cast(label_collection.GetProperty()); + mutation_state = p_label->GetColour(); + } + p_cell_color_reference_data->InsertNextTuple1(mutation_state); + } + else if (mColorByCellLabel) + { + double label = 0.0; + if (p_biological_cell->HasCellProperty()) + { + CellPropertyCollection collection = p_biological_cell->rGetCellPropertyCollection().GetProperties(); + boost::shared_ptr p_label = boost::static_pointer_cast(collection.GetProperty()); + label = p_label->GetColour(); + } + p_cell_color_reference_data->InsertNextTuple1(label); + } + else + { + p_cell_color_reference_data->InsertNextTuple1(p_biological_cell->GetCellId()); + } + } + else + { + p_cell_color_reference_data->InsertNextTuple1(-1.0); + } + } + } + + p_voronoi_grid->GetCellData()->AddArray(p_cell_color_reference_data); + p_voronoi_grid->GetCellData()->SetScalars(p_cell_color_reference_data); + + auto p_scaled_ctf = vtkSmartPointer::New(); + if (!mColorByCellData) + { + double range[2]; + p_voronoi_grid->GetCellData()->GetArray("CellColors")->GetRange(range); + for (unsigned idx = 0; idx < 255; idx++) + { + double color[3]; + this->mpDiscreteColorTransferFunction->GetColor((255.0 - double(idx)) / 255.0, color); + p_scaled_ctf->AddRGBPoint(range[0] + double(idx) * (range[1] - range[0]) / 255.0, color[0], color[1], color[2]); + } + } + else + { + double range[2]; + p_voronoi_grid->GetCellData()->GetArray("CellColors")->GetRange(range); + for (unsigned idx = 0; idx < 255; idx++) + { + double color[3]; + this->mpColorTransferFunction->GetColor(double(idx) / 255.0, color); + p_scaled_ctf->AddRGBPoint(range[0] + double(idx) * (range[1] - range[0]) / 255.0, color[0], color[1], color[2]); + } + } + p_scaled_ctf->Build(); + + auto p_threshold = vtkSmartPointer::New(); + p_threshold->SetInputData(p_voronoi_grid); +#if (VTK_MAJOR_VERSION < 9 || (VTK_MAJOR_VERSION == 9 && VTK_MINOR_VERSION < 1)) // VTK < 9.1 + p_threshold->ThresholdByUpper(0.0); +#else + p_threshold->SetUpperThreshold(0.0); + p_threshold->SetThresholdFunction(vtkThreshold::THRESHOLD_UPPER); +#endif + p_threshold->SetInputArrayToProcess(0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_CELLS, "CellColors"); + + auto p_geom_filter = vtkSmartPointer::New(); + p_geom_filter->SetInputConnection(p_threshold->GetOutputPort()); + + auto p_mapper = vtkSmartPointer::New(); + p_mapper->SetInputConnection(p_geom_filter->GetOutputPort()); + p_mapper->SetLookupTable(p_scaled_ctf); + p_mapper->ScalarVisibilityOn(); + // p_grid_mapper->InterpolateScalarsBeforeMappingOn(); + p_mapper->SelectColorArray("CellColors"); + p_mapper->SetScalarModeToUseCellData(); + p_mapper->SetColorModeToMapScalars(); + + auto p_actor = vtkSmartPointer::New(); + p_actor->SetMapper(p_mapper); + p_actor->GetProperty()->SetOpacity(this->mVolumeOpacity); + if (mColorCellByUserDefined) + { + p_actor->GetProperty()->SetColor(this->mPointColor[0], this->mPointColor[1], this->mPointColor[2]); + } + pRenderer->AddActor(p_actor); + + auto p_voronoi_extract_edges = vtkSmartPointer::New(); + p_voronoi_extract_edges->SetInputConnection(p_geom_filter->GetOutputPort()); + p_voronoi_extract_edges->SetFeatureEdges(false); + p_voronoi_extract_edges->SetBoundaryEdges(true); + p_voronoi_extract_edges->SetManifoldEdges(true); + p_voronoi_extract_edges->SetNonManifoldEdges(false); + + auto p_voronoi_tubes = vtkSmartPointer::New(); + p_voronoi_tubes->SetInputConnection(p_voronoi_extract_edges->GetOutputPort()); + p_voronoi_tubes->SetRadius(this->mEdgeSize); + p_voronoi_tubes->SetNumberOfSides(12); + + auto p_voronoi_tube_mapper = vtkSmartPointer::New(); + p_voronoi_tube_mapper->SetInputConnection(p_voronoi_tubes->GetOutputPort()); + p_voronoi_tube_mapper->ScalarVisibilityOff(); + + auto p_voronoi_tube_actor = vtkSmartPointer::New(); + p_voronoi_tube_actor->SetMapper(p_voronoi_tube_mapper); + p_voronoi_tube_actor->GetProperty()->SetColor(this->mEdgeColor[0], this->mEdgeColor[1], this->mEdgeColor[2]); + pRenderer->AddActor(p_voronoi_tube_actor); + + if (!this->mDataLabel.empty() && this->mShowScaleBar) + { + this->mpScaleBar->SetLookupTable(p_scaled_ctf); + this->mpScaleBar->SetTitle(this->mDataLabel.c_str()); + pRenderer->AddActor(this->mpScaleBar); + } + } + + if (mShowMutableMeshEdges) + { + // Do the mutable mesh + // Make the local mesh into a VtkMesh + auto p_mutable_grid = vtkSmartPointer::New(); + auto p_points = vtkSmartPointer::New(); + p_points->GetData()->SetName("Vertex positions"); + + for (typename AbstractMesh::NodeIterator node_iter = p_cell_population->rGetMesh().GetNodeIteratorBegin(); + node_iter != p_cell_population->rGetMesh().GetNodeIteratorEnd(); + ++node_iter) + { + c_vector current_item = node_iter->rGetLocation(); + if (DIM == 3) + { + p_points->InsertNextPoint(current_item[0], current_item[1], current_item[2]); + } + else if (DIM == 2) + { + p_points->InsertNextPoint(current_item[0], current_item[1], 0.0); + } + else // (DIM == 1) + { + p_points->InsertNextPoint(current_item[0], 0.0, 0.0); + } + } + + p_mutable_grid->SetPoints(p_points); + + for (typename AbstractTetrahedralMesh::ElementIterator elem_iter = p_cell_population->rGetMesh().GetElementIteratorBegin(); + elem_iter != p_cell_population->rGetMesh().GetElementIteratorEnd(); + ++elem_iter) + { + + vtkSmartPointer p_cell; + if (DIM == 3) + { + p_cell = vtkSmartPointer::New(); + } + else if (DIM == 2) + { + p_cell = vtkSmartPointer::New(); + } + else //(DIM == 1) + { + p_cell = vtkSmartPointer::New(); + } + vtkSmartPointer p_cell_id_list = p_cell->GetPointIds(); + for (unsigned j = 0; j < DIM + 1; ++j) + { + unsigned global_node_index = elem_iter->GetNodeGlobalIndex(j); + p_cell_id_list->SetId(j, global_node_index); + } + p_mutable_grid->InsertNextCell(p_cell->GetCellType(), p_cell_id_list); + } + + auto p_mutable_geom_filter = vtkSmartPointer::New(); + p_mutable_geom_filter->SetInputData(p_mutable_grid); + + auto p_extract_edges = vtkSmartPointer::New(); + p_extract_edges->SetInputConnection(p_mutable_geom_filter->GetOutputPort()); + p_extract_edges->SetFeatureEdges(false); + p_extract_edges->SetBoundaryEdges(true); + p_extract_edges->SetManifoldEdges(true); + p_extract_edges->SetNonManifoldEdges(false); + + auto p_mutable_tubes = vtkSmartPointer::New(); + p_mutable_tubes->SetInputConnection(p_extract_edges->GetOutputPort()); + p_mutable_tubes->SetRadius(0.02); + p_mutable_tubes->SetNumberOfSides(12); + + auto p_mutable_mapper = vtkSmartPointer::New(); + p_mutable_mapper->SetInputConnection(p_mutable_tubes->GetOutputPort()); + + auto p_mutable_actor = vtkSmartPointer::New(); + p_mutable_actor->SetMapper(p_mutable_mapper); + p_mutable_actor->GetProperty()->SetColor(1, 1, 1); + pRenderer->AddActor(p_mutable_actor); + } +} + +template +void CellPopulationPyChasteActorGenerator::SetCellPopulation(boost::shared_ptr > pCellPopulation) +{ + this->mpCellPopulation = pCellPopulation; +} + +template +void CellPopulationPyChasteActorGenerator::SetShowMutableMeshEdges(bool showEdges) +{ + mShowMutableMeshEdges = showEdges; +} + +template +void CellPopulationPyChasteActorGenerator::SetShowVoronoiMeshEdges(bool showEdges) +{ + mShowVoronoiMeshEdges = showEdges; +} + +template +void CellPopulationPyChasteActorGenerator::SetColorByUserDefined(bool colorByCellUserDefined) +{ + mColorCellByUserDefined = colorByCellUserDefined; +} + +template +void CellPopulationPyChasteActorGenerator::SetShowPottsMeshEdges(bool showEdges) +{ + mShowPottsMeshEdges = showEdges; +} + +template +void CellPopulationPyChasteActorGenerator::SetColorByCellMutationState(bool colorByCellMutationState) +{ + mColorByCellMutationState = colorByCellMutationState; +} + +template +void CellPopulationPyChasteActorGenerator::SetColorByCellLabel(bool colorByCellLabel) +{ + mColorByCellLabel = colorByCellLabel; +} + +template +void CellPopulationPyChasteActorGenerator::SetShowPottsMeshOutlines(bool showEdges) +{ + mShowPottsMeshOutlines = showEdges; +} + +template +void CellPopulationPyChasteActorGenerator::SetColorByCellType(bool colorByCellType) +{ + mColorByCellType = colorByCellType; +} + +template +void CellPopulationPyChasteActorGenerator::SetColorByCellData(bool colorByCellData) +{ + mColorByCellData = colorByCellData; +} + +template +void CellPopulationPyChasteActorGenerator::SetShowCellCentres(bool showCentres) +{ + mShowCellCentres = showCentres; +} + +template class CellPopulationPyChasteActorGenerator<2>; +template class CellPopulationPyChasteActorGenerator<3>; diff --git a/pychaste/src/cpp/visualization/CellPopulationPyChasteActorGenerator.hpp b/pychaste/src/cpp/visualization/CellPopulationPyChasteActorGenerator.hpp new file mode 100644 index 0000000000..5ab8ea811d --- /dev/null +++ b/pychaste/src/cpp/visualization/CellPopulationPyChasteActorGenerator.hpp @@ -0,0 +1,214 @@ +/* + +Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef CELLPOPULATIONPYCHASTEACTORGENERATOR_HPP_ +#define CELLPOPULATIONPYCHASTEACTORGENERATOR_HPP_ + +#include +#include +#include +#include +#include "AbstractCellPopulation.hpp" +#include "AbstractPyChasteActorGenerator.hpp" +#include "MeshBasedCellPopulation.hpp" +#include "SmartPointers.hpp" +#include "VertexBasedCellPopulation.hpp" + +/** + * This class generates VTK actors for CellPopulations + */ +template +class CellPopulationPyChasteActorGenerator : public AbstractPyChasteActorGenerator +{ + /** + * The CellPopulation + */ + boost::shared_ptr > mpCellPopulation; + + /** + * Show mutable mesh edges for Mesh Based populations + */ + bool mShowMutableMeshEdges; + + /** + * Show voronoi mesh edges for Mesh and Vertex Based populations + */ + bool mShowVoronoiMeshEdges; + + /** + * Show Potts mesh edges for Ca and Potts Based populations + */ + bool mShowPottsMeshEdges; + + /** + * Show Potts mesh outlines + */ + bool mShowPottsMeshOutlines; + + /** + * Color the cells by type + */ + bool mColorByCellType; + + /** + * Color the cells by data + */ + bool mColorByCellData; + + /** + * Color the cells by mutation state + */ + bool mColorByCellMutationState; + + /** + * Color the cells by label + */ + bool mColorByCellLabel; + + /** + * Whether to show the cell centres + */ + bool mShowCellCentres; + + /** + * Color cells using a user defined color + */ + bool mColorCellByUserDefined; + +public: + /** + * Constructor + */ + CellPopulationPyChasteActorGenerator(); + + /** + * Destructor + */ + ~CellPopulationPyChasteActorGenerator(); + + /** + * Add the CellPopulation actor to the renderer + * @param pRenderer the current renderer + */ + void AddActor(vtkSmartPointer pRenderer); + + /** + * Specialized class for adding Mesh based population + * @param pRenderer the current renderer + */ + void AddMeshBasedCellPopulationActor(vtkSmartPointer pRenderer); + + /** + * Specialized class for adding Mesh based population + * @param pRenderer the current renderer + */ + void AddVertexBasedCellPopulationActor(vtkSmartPointer pRenderer); + + /** + * Specialized class for adding Immersed boundary population + * @param pRenderer the current renderer + */ + void AddImmersedBoundaryCellPopulationActor(vtkSmartPointer pRenderer); + + /** + * Specialized class for adding Ca based population + * @param pRenderer the current renderer + */ + void AddCaBasedCellPopulationActor(vtkSmartPointer pRenderer); + + /** + * Specialized class for adding Potts based population + * @param pRenderer the current renderer + */ + void AddPottsBasedCellPopulationActor(vtkSmartPointer pRenderer); + + /** + * Set the CellPopulation + * @param pCellPopulation the CellPopulation to render + */ + void SetCellPopulation(boost::shared_ptr > pCellPopulation); + + /** + * @param showEdges show the voronoi mesh + */ + void SetShowVoronoiMeshEdges(bool showEdges); + + /** + * @param showEdges show the mutable mesh + */ + void SetShowMutableMeshEdges(bool showEdges); + + /** + * @param showEdges show the potts mesh + */ + void SetShowPottsMeshEdges(bool showEdges); + + /** + * @param showOutlines show the outlines of Potts cells + */ + void SetShowPottsMeshOutlines(bool showOutlines); + + /** + * @param colorByCellType color cells by type + */ + void SetColorByCellType(bool colorByCellType); + + /** + * @param colorByCellType color cells by mutation state + */ + void SetColorByCellMutationState(bool colorByCellMutationState); + + /** + * @param colorByCellType color cells by label + */ + void SetColorByCellLabel(bool colorByCellLabel); + + /** + * @param colorByCellType color cells by the user defined point color + */ + void SetColorByUserDefined(bool colorByCellUserDefined); + + /** + * @param colorByCellData color cells by data + */ + void SetColorByCellData(bool colorByCellData); + + /** + * @param showCentres show cell centres + */ + void SetShowCellCentres(bool showCentres); +}; + +#endif // CELLPOPULATIONPYCHASTEACTORGENERATOR_HPP_ diff --git a/pychaste/src/cpp/visualization/VtkScene.cpp b/pychaste/src/cpp/visualization/VtkScene.cpp new file mode 100644 index 0000000000..5ea9dcdda0 --- /dev/null +++ b/pychaste/src/cpp/visualization/VtkScene.cpp @@ -0,0 +1,307 @@ +/* + +Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Exception.hpp" +#include "UblasIncludes.hpp" +#include "UblasVectorInclude.hpp" + +#include "VtkScene.hpp" + +// For some reason an explicit interactor style is needed capture mouse events +class customMouseInteractorStyle : public vtkInteractorStyleTrackballCamera +{ +public: + static customMouseInteractorStyle* New(); + vtkTypeMacro(customMouseInteractorStyle, vtkInteractorStyleTrackballCamera); + + virtual void OnLeftButtonDown() + { + // Forward events + vtkInteractorStyleTrackballCamera::OnLeftButtonDown(); + } + + virtual void OnMiddleButtonDown() + { + // Forward events + vtkInteractorStyleTrackballCamera::OnMiddleButtonDown(); + } + + virtual void OnRightButtonDown() + { + // Forward events + vtkInteractorStyleTrackballCamera::OnRightButtonDown(); + } +}; + +vtkStandardNewMacro(customMouseInteractorStyle); + +template +VtkScene::VtkScene() + : mpRenderer(vtkSmartPointer::New()), + mpRenderWindow(vtkSmartPointer::New()), + mpRenderWindowInteractor(vtkSmartPointer::New()), + mOutputFilePath(), + mAnimationWriter(vtkSmartPointer::New()), + mWindowToImageFilter(vtkSmartPointer::New()), + mIsInteractive(false), + mSaveAsAnimation(false), + mSaveAsImages(false), + mHasStarted(false), + mAddAnnotations(false), + mOutputFrequency(1), + mpCellPopulationGenerator(boost::make_shared >()) +{ + mpRenderer->SetBackground(1.0, 1.0, 1.0); + mpRenderWindow->AddRenderer(mpRenderer); + mpRenderWindow->SetSize(800.0, 600.0); + mpRenderWindowInteractor->SetRenderWindow(mpRenderWindow); + + auto style = vtkSmartPointer::New(); + mpRenderWindowInteractor->SetInteractorStyle(style); +} + +template +VtkScene::~VtkScene() +{ +} + +template +boost::shared_ptr > VtkScene::GetCellPopulationActorGenerator() +{ + return mpCellPopulationGenerator; +} + +template +void VtkScene::SetIsInteractive(bool isInteractive) +{ + mIsInteractive = isInteractive; +} + +template +vtkSmartPointer VtkScene::GetRenderer() +{ + return mpRenderer; +} + +template +void VtkScene::SetSaveAsImages(bool saveAsImages) +{ + mSaveAsImages = saveAsImages; +} + +template +vtkSmartPointer VtkScene::GetSceneAsCharBuffer() +{ + ResetRenderer(0); + + mpRenderWindow->SetOffScreenRendering(1); + mpRenderWindow->Render(); + mWindowToImageFilter->Modified(); + + auto p_writer = vtkSmartPointer::New(); + p_writer->SetWriteToMemory(1); + p_writer->SetInputConnection(mWindowToImageFilter->GetOutputPort()); + p_writer->Write(); + + return p_writer->GetResult(); +} + +template +void VtkScene::ResetRenderer(unsigned time_step) +{ + if (!mHasStarted) + { + Start(); + } + + vtkSmartPointer p_actor; + vtkSmartPointer p_actors = mpRenderer->GetActors(); + + for (p_actors->InitTraversal(); (p_actor = p_actors->GetNextItem()) != NULL;) + { + mpRenderer->RemoveActor(p_actor); + } + + if (mpCellPopulationGenerator) + { + mpCellPopulationGenerator->AddActor(mpRenderer); + } + mpRenderer->ResetCamera(); + + if (mSaveAsImages) + { + mpRenderWindow->SetOffScreenRendering(1); + mpRenderWindow->Render(); + mWindowToImageFilter->Modified(); + + auto p_writer = vtkSmartPointer::New(); + p_writer->SetWriteToMemory(1); + p_writer->SetInputConnection(mWindowToImageFilter->GetOutputPort()); + + if (!mOutputFilePath.empty()) + { + p_writer->SetWriteToMemory(0); + p_writer->SetFileName((mOutputFilePath + "_" + boost::lexical_cast(time_step) + ".png").c_str()); + p_writer->Write(); + } + } + + if (mSaveAsAnimation) + { + if (!mSaveAsImages) + { + mpRenderWindow->SetOffScreenRendering(1); + mpRenderWindow->Render(); + mWindowToImageFilter->Modified(); + } + mAnimationWriter->Write(); + } + + if (mIsInteractive) + { + mpRenderWindow->SetOffScreenRendering(0); + mpRenderWindow->Render(); + } +} + +template +void VtkScene::SetOutputFilePath(const std::string& rPath) +{ + mOutputFilePath = rPath; +} + +template +void VtkScene::SetSaveAsAnimation(bool saveAsAnimation) +{ + mSaveAsAnimation = saveAsAnimation; +} + +template +void VtkScene::SetCellPopulation(boost::shared_ptr > pCellPopulation) +{ + mpCellPopulationGenerator->SetCellPopulation(pCellPopulation); +} + +template +void VtkScene::End() +{ + if (mSaveAsAnimation and mHasStarted) + { + mAnimationWriter->End(); + } +} + +template +void VtkScene::Start() +{ + mpRenderer->ResetCamera(); + if (DIM == 3) + { + mpRenderer->GetActiveCamera()->Azimuth(45.0); + } + + if (!mIsInteractive) + { + mpRenderWindow->SetOffScreenRendering(1); + } + + if (mSaveAsImages || mSaveAsAnimation) + { + mpRenderWindow->SetOffScreenRendering(1); + mpRenderWindow->Render(); + mWindowToImageFilter->SetInput(mpRenderWindow); + mWindowToImageFilter->Update(); + } + + if (mSaveAsAnimation) + { + mAnimationWriter->SetInputConnection(mWindowToImageFilter->GetOutputPort()); + mAnimationWriter->SetFileName((mOutputFilePath + ".ogg").c_str()); + mAnimationWriter->SetRate(1.0); + mAnimationWriter->Start(); + } + + mHasStarted = true; + ResetRenderer(); + + if (mIsInteractive) + { + mpRenderWindowInteractor->Initialize(); + } +} + +template +void VtkScene::StartInteractiveEventHandler() +{ + mpRenderWindowInteractor->Start(); +} + +template class VtkScene<2>; +template class VtkScene<3>; diff --git a/pychaste/src/cpp/visualization/VtkScene.hpp b/pychaste/src/cpp/visualization/VtkScene.hpp new file mode 100644 index 0000000000..705517ce16 --- /dev/null +++ b/pychaste/src/cpp/visualization/VtkScene.hpp @@ -0,0 +1,217 @@ +/* + +Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef VTKSCENE_HPP_ +#define VTKSCENE_HPP_ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if VTK_MAJOR_VERSION <= 6 +VTK_MODULE_INIT(vtkRenderingOpenGL); +#else +VTK_MODULE_INIT(vtkRenderingOpenGL2); +#endif +VTK_MODULE_INIT(vtkRenderingFreeType); + +#include "AbstractCellPopulation.hpp" +#include "CellPopulationPyChasteActorGenerator.hpp" +#include "MeshBasedCellPopulation.hpp" +#include "SmartPointers.hpp" + +/** + * A simple VTK renderer for cell populations + */ +template +class VtkScene +{ + /** + * The vtk renderer + */ + vtkSmartPointer mpRenderer; + + /** + * The vtk render window + */ + vtkSmartPointer mpRenderWindow; + + /** + * The vtk render window interactor + */ + vtkSmartPointer mpRenderWindowInteractor; + + /** + * The path for output + */ + std::string mOutputFilePath; + + /** + * The animation writer + */ + vtkSmartPointer mAnimationWriter; + + /** + * The image to window filter + */ + vtkSmartPointer mWindowToImageFilter; + + /** + * Is the rendering interactive + */ + bool mIsInteractive; + + /** + * Save as an animation + */ + bool mSaveAsAnimation; + + /** + * Save as an image + */ + bool mSaveAsImages; + + /** + * Has the renderer started + */ + bool mHasStarted; + + /** + * Add annotation + */ + bool mAddAnnotations; + + /** + * How often to update the renderer during a simulation + */ + unsigned mOutputFrequency; + + /** + * The cell population + */ + boost::shared_ptr > mpCellPopulationGenerator; + +public: + /** + * Constructor + */ + VtkScene(); + + /** + * Destructor + */ + virtual ~VtkScene(); + + /** + * Shut down the scene and close the animation + */ + void End(); + + /** + * Render the current scene and return it as a char array that can be passed + * into a Python buffer for display. + * @return the scene as a char array + */ + vtkSmartPointer GetSceneAsCharBuffer(); + + /** + * Return the renderer + * @return the vtk renderer + */ + vtkSmartPointer GetRenderer(); + + /** + * Get the cell population actor generator + * @return the cell population actor generator + */ + boost::shared_ptr > GetCellPopulationActorGenerator(); + + /** + * Update the renderer, this will update the population actor and write output images + * @param timeStep the curren time step, for annotating output files + */ + virtual void ResetRenderer(unsigned timeStep = 0); + + /** + * Render the scene + */ + void Start(); + + /** + * Set the cell population + * @param pCellPopulation the cell population for rendering + */ + void SetCellPopulation(boost::shared_ptr > pCellPopulation); + + /** + * Set the path for output + * @param rPath the path for output + */ + void SetOutputFilePath(const std::string& rPath); + + /** + * Set run as an interactive window + * @param isInteractive run as an interactive window + */ + void SetIsInteractive(bool isInteractive); + + /** + * Whether to save as an animation + * @param saveAsAnimation save as an animation + */ + void SetSaveAsAnimation(bool saveAsAnimation); + + /** + * Whether to save as images (default) + * @param saveAsImages save as images + */ + void SetSaveAsImages(bool saveAsImages); + + /** + * Start the event handler for window interaction + */ + void StartInteractiveEventHandler(); +}; + +#endif // VTKSCENE_HPP_ diff --git a/pychaste/src/py/LICENSE b/pychaste/src/py/LICENSE new file mode 100644 index 0000000000..c9047c4820 --- /dev/null +++ b/pychaste/src/py/LICENSE @@ -0,0 +1,32 @@ +BSD 3-Clause License. + +Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/pychaste/src/py/MANIFEST.in b/pychaste/src/py/MANIFEST.in new file mode 100644 index 0000000000..90330d680d --- /dev/null +++ b/pychaste/src/py/MANIFEST.in @@ -0,0 +1,2 @@ +recursive-include chaste/ *.so +recursive-include doc/tutorial/ *.ipynb diff --git a/pychaste/src/py/chaste/__init__.py b/pychaste/src/py/chaste/__init__.py new file mode 100644 index 0000000000..7892a884d3 --- /dev/null +++ b/pychaste/src/py/chaste/__init__.py @@ -0,0 +1,72 @@ +"""PyChaste Module""" + +__copyright__ = """Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import os +import sys + +import petsc4py + +import chaste.cell_based +import chaste.core +import chaste.mesh +import chaste.ode +import chaste.pde +import chaste.visualization + + +def init(test_output=None, comm=None): + """ + Initialize PETSc and set the CHASTE_TEST_OUTPUT environment variable. + + :param test_output: The CHASTE_TEST_OUTPUT directory. + :param comm: MPI communicator. + """ + # Set CHASTE_TEST_OUTPUT + if test_output: + # Set to user specified value if provided + os.environ["CHASTE_TEST_OUTPUT"] = test_output + elif os.environ.get("CHASTE_TEST_OUTPUT") is None: + # Set to the current working directory if not already set + os.environ["CHASTE_TEST_OUTPUT"] = os.getcwd() + + # Initialize PETSc + if comm is None: + petsc4py.init(sys.argv) + else: + petsc4py.init(comm=comm) + + return chaste.core.OutputFileHandler("", False) + + +init() diff --git a/pychaste/src/py/chaste/_syntax.py b/pychaste/src/py/chaste/_syntax.py new file mode 100644 index 0000000000..3fe5fd7dad --- /dev/null +++ b/pychaste/src/py/chaste/_syntax.py @@ -0,0 +1,98 @@ +"""Syntax Module""" + +__copyright__ = """Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import inspect +import warnings +from collections.abc import Iterable +from typing import Dict, Tuple, Type + + +class TemplateClassDict: + """ + Allows using class syntax like Foo[2, 2](...) in place of Foo_2_2(...) + + Usage: + >>> Foo = TemplateClassDict({ ("2", "2"): Foo_2_2, ("3", "3"): Foo_3_3 }) + """ + + def __init__(self, template_dict: Dict[Tuple[str, ...], Type]) -> None: + """ + :param template_dict: A dictionary mapping template arg tuples to classes + """ + self._dict = {} + for arg_tuple, cls in template_dict.items(): + if not inspect.isclass(cls): + raise TypeError("Expected class, got {}".format(type(cls))) + if not isinstance(arg_tuple, Iterable): + arg_tuple = (arg_tuple,) + key = tuple( + arg.__name__ if inspect.isclass(arg) else str(arg) for arg in arg_tuple + ) + self._dict[key] = cls + + def __getitem__(self, arg_tuple: Tuple[str, ...]) -> Type: + if not isinstance(arg_tuple, Iterable): + arg_tuple = (arg_tuple,) + key = tuple( + arg.__name__ if inspect.isclass(arg) else str(arg) for arg in arg_tuple + ) + return self._dict[key] + + +class DeprecatedClass: + """ + Warns when a deprecated class is used and switches to the correct class. + + Usage: + >>> Foo2_2 = DeprecatedClass("Foo2_2", Foo_2_2) + """ + def __init__(self, old_name: str, new_class: Type): + self.old_name = old_name + self.new_class = new_class + + self.new_syntax = self.new_class.__name__ + if "_" in self.new_syntax: + # Recommend using Foo["2", "2"]() instead of Foo2_2() + base_name, *params = self.new_syntax.split("_") + params = [f'"{param}"' for param in params] + self.new_syntax = f'{base_name}[{", ".join(params)}]' + + def __call__(self, *args, **kwargs): + warnings.warn( + f"{self.old_name} is deprecated and will be removed in a future version. " + f"Please use {self.new_syntax} instead.", + DeprecationWarning, + stacklevel=2, + ) + return self.new_class(*args, **kwargs) diff --git a/pychaste/src/py/chaste/cell_based/__init__.py b/pychaste/src/py/chaste/cell_based/__init__.py new file mode 100644 index 0000000000..8ad306ce09 --- /dev/null +++ b/pychaste/src/py/chaste/cell_based/__init__.py @@ -0,0 +1,1532 @@ +"""Cell-Based Module""" + +__copyright__ = """Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +from chaste._pychaste_all import ( + AdhesionPottsUpdateRule_2, + AdhesionPottsUpdateRule_3, + Alarcon2004OxygenBasedCellCycleModel, + AlwaysDivideCellCycleModel, + ApcOneHitCellMutationState, + ApcTwoHitCellMutationState, + ApoptoticCellKiller_2, + ApoptoticCellKiller_3, + ApoptoticCellProperty, + AttractingPlaneBoundaryCondition_2_2, + AttractingPlaneBoundaryCondition_3_3, + BernoulliTrialCellCycleModel, + BetaCateninOneHitCellMutationState, + BiasedBernoulliTrialCellCycleModel, + BoundaryNodeWriter_2_2, + BoundaryNodeWriter_3_3, + BuskeAdhesiveForce_2, + BuskeAdhesiveForce_3, + BuskeCompressionForce_2, + BuskeCompressionForce_3, + BuskeElasticForce_2, + BuskeElasticForce_3, + CaBasedCellPopulation_2, + CaBasedCellPopulation_3, + Cell, + CellAgesWriter_2_2, + CellAgesWriter_3_3, + CellAncestor, + CellAncestorWriter_2_2, + CellAncestorWriter_3_3, + CellAppliedForceWriter_2_2, + CellAppliedForceWriter_3_3, + CellCycleModelProteinConcentrationsWriter_2_2, + CellCycleModelProteinConcentrationsWriter_3_3, + CellData, + CellDataItemWriter_2_2, + CellDataItemWriter_3_3, + CellDeltaNotchWriter_2_2, + CellDeltaNotchWriter_3_3, + CellDivisionLocationsWriter_2_2, + CellDivisionLocationsWriter_3_3, + CellEdgeData, + CellId, + CellIdWriter_2_2, + CellIdWriter_3_3, + CellLabel, + CellLabelWriter_2_2, + CellLabelWriter_3_3, + CellLocationIndexWriter_2_2, + CellLocationIndexWriter_3_3, + CellMutationStatesCountWriter_2_2, + CellMutationStatesCountWriter_3_3, + CellMutationStatesWriter_2_2, + CellMutationStatesWriter_3_3, + CellPopulationAdjacencyMatrixWriter_2_2, + CellPopulationAdjacencyMatrixWriter_3_3, + CellPopulationAreaWriter_2_2, + CellPopulationAreaWriter_3_3, + CellPopulationElementWriter_2_2, + CellPopulationElementWriter_3_3, + CellProliferativePhasesCountWriter_2_2, + CellProliferativePhasesCountWriter_3_3, + CellProliferativePhasesWriter_2_2, + CellProliferativePhasesWriter_3_3, + CellProliferativeTypesCountWriter_2_2, + CellProliferativeTypesCountWriter_3_3, + CellProliferativeTypesWriter_2_2, + CellProliferativeTypesWriter_3_3, + CellPropertyCollection, + CellPropertyRegistry, + CellRadiusWriter_2_2, + CellRadiusWriter_3_3, + CellRemovalLocationsWriter_2_2, + CellRemovalLocationsWriter_3_3, + CellRosetteRankWriter_2_2, + CellRosetteRankWriter_3_3, + CellsGenerator_Alarcon2004OxygenBasedCellCycleModel_2, + CellsGenerator_Alarcon2004OxygenBasedCellCycleModel_3, + CellsGenerator_AlwaysDivideCellCycleModel_2, + CellsGenerator_AlwaysDivideCellCycleModel_3, + CellsGenerator_BernoulliTrialCellCycleModel_2, + CellsGenerator_BernoulliTrialCellCycleModel_3, + CellsGenerator_BiasedBernoulliTrialCellCycleModel_2, + CellsGenerator_BiasedBernoulliTrialCellCycleModel_3, + CellsGenerator_ContactInhibitionCellCycleModel_2, + CellsGenerator_ContactInhibitionCellCycleModel_3, + CellsGenerator_ExponentialG1GenerationalCellCycleModel_2, + CellsGenerator_ExponentialG1GenerationalCellCycleModel_3, + CellsGenerator_FixedG1GenerationalCellCycleModel_2, + CellsGenerator_FixedG1GenerationalCellCycleModel_3, + CellsGenerator_FixedSequenceCellCycleModel_2, + CellsGenerator_FixedSequenceCellCycleModel_3, + CellsGenerator_GammaG1CellCycleModel_2, + CellsGenerator_GammaG1CellCycleModel_3, + CellsGenerator_LabelDependentBernoulliTrialCellCycleModel_2, + CellsGenerator_LabelDependentBernoulliTrialCellCycleModel_3, + CellsGenerator_NoCellCycleModel_2, + CellsGenerator_NoCellCycleModel_3, + CellsGenerator_SimpleOxygenBasedCellCycleModel_2, + CellsGenerator_SimpleOxygenBasedCellCycleModel_3, + CellsGenerator_StochasticOxygenBasedCellCycleModel_2, + CellsGenerator_StochasticOxygenBasedCellCycleModel_3, + CellsGenerator_TysonNovakCellCycleModel_2, + CellsGenerator_TysonNovakCellCycleModel_3, + CellsGenerator_UniformCellCycleModel_2, + CellsGenerator_UniformCellCycleModel_3, + CellsGenerator_UniformG1GenerationalCellCycleModel_2, + CellsGenerator_UniformG1GenerationalCellCycleModel_3, + CellSrnModel, + CellVolumesWriter_2_2, + CellVolumesWriter_3_3, + ChemotacticForce_2, + ChemotacticForce_3, + ChemotaxisPottsUpdateRule_2, + ChemotaxisPottsUpdateRule_3, + ContactInhibitionCellCycleModel, + DefaultCellProliferativeType, + DeltaNotchEdgeInteriorTrackingModifier_2, + DeltaNotchEdgeInteriorTrackingModifier_3, + DeltaNotchEdgeSrnModel, + DeltaNotchEdgeTrackingModifier_2, + DeltaNotchEdgeTrackingModifier_3, + DeltaNotchInteriorSrnModel, + DeltaNotchSrnModel, + DeltaNotchTrackingModifier_2, + DeltaNotchTrackingModifier_3, + DifferentialAdhesionGeneralisedLinearSpringForce_2_2, + DifferentialAdhesionGeneralisedLinearSpringForce_3_3, + DifferentialAdhesionPottsUpdateRule_2, + DifferentialAdhesionPottsUpdateRule_3, + DifferentiatedCellProliferativeType, + DiffusionCaUpdateRule_2, + DiffusionCaUpdateRule_3, + DiffusionForce_2, + DiffusionForce_3, + DivisionBiasTrackingModifier_2, + DivisionBiasTrackingModifier_3, + ExclusionCaBasedDivisionRule_2, + ExclusionCaBasedDivisionRule_3, + ExponentialG1GenerationalCellCycleModel, + ExtrinsicPullModifier_2, + ExtrinsicPullModifier_3, + FarhadifarForce_2, + FarhadifarForce_3, + FixedCentreBasedDivisionRule_2_2, + FixedCentreBasedDivisionRule_3_3, + FixedG1GenerationalCellCycleModel, + FixedSequenceCellCycleModel, + FixedVertexBasedDivisionRule_2, + FixedVertexBasedDivisionRule_3, + ForwardEulerNumericalMethod_2_2, + ForwardEulerNumericalMethod_3_3, + GammaG1CellCycleModel, + GeneralisedLinearSpringForce_2_2, + GeneralisedLinearSpringForce_3_3, + Goldbeter1991SrnModel, + HeterotypicBoundaryLengthWriter_2_2, + HeterotypicBoundaryLengthWriter_3_3, + ImmersedBoundaryBoundaryCellWriter_2_2, + ImmersedBoundaryBoundaryCellWriter_3_3, + ImmersedBoundaryCellPopulation_2, + ImmersedBoundaryCellPopulation_3, + ImmersedBoundaryKinematicFeedbackForce_2, + ImmersedBoundaryKinematicFeedbackForce_3, + ImmersedBoundaryLinearDifferentialAdhesionForce_2, + ImmersedBoundaryLinearDifferentialAdhesionForce_3, + ImmersedBoundaryLinearInteractionForce_2, + ImmersedBoundaryLinearInteractionForce_3, + ImmersedBoundaryLinearMembraneForce_2, + ImmersedBoundaryLinearMembraneForce_3, + ImmersedBoundaryMorseInteractionForce_2, + ImmersedBoundaryMorseInteractionForce_3, + ImmersedBoundaryMorseMembraneForce_2, + ImmersedBoundaryMorseMembraneForce_3, + ImmersedBoundaryNeighbourNumberWriter_2_2, + ImmersedBoundaryNeighbourNumberWriter_3_3, + ImmersedBoundarySimulationModifier_2, + ImmersedBoundarySimulationModifier_3, + ImmersedBoundarySvgWriter_2, + ImmersedBoundarySvgWriter_3, + IsolatedLabelledCellKiller_2, + IsolatedLabelledCellKiller_3, + LabelDependentBernoulliTrialCellCycleModel, + LegacyCellProliferativeTypesWriter_2_2, + LegacyCellProliferativeTypesWriter_3_3, + MeshBasedCellPopulation_2_2, + MeshBasedCellPopulation_3_3, + MeshBasedCellPopulationWithGhostNodes_2, + MeshBasedCellPopulationWithGhostNodes_3, + NagaiHondaDifferentialAdhesionForce_2, + NagaiHondaDifferentialAdhesionForce_3, + NagaiHondaForce_2, + NagaiHondaForce_3, + NoCellCycleModel, + NodeBasedCellPopulation_2, + NodeBasedCellPopulation_3, + NodeBasedCellPopulationWithBuskeUpdate_2, + NodeBasedCellPopulationWithBuskeUpdate_3, + NodeBasedCellPopulationWithParticles_2, + NodeBasedCellPopulationWithParticles_3, + NodeLocationWriter_2_2, + NodeLocationWriter_3_3, + NodeVelocityWriter_2_2, + NodeVelocityWriter_3_3, + NormallyDistributedTargetAreaModifier_2, + NormallyDistributedTargetAreaModifier_3, + NullSrnModel, + OffLatticeSimulation_2_2, + OffLatticeSimulation_3_3, + OnLatticeSimulation_2, + OnLatticeSimulation_3, + PlanarPolarisedFarhadifarForce_2, + PlanarPolarisedFarhadifarForce_3, + PlaneBasedCellKiller_2, + PlaneBasedCellKiller_3, + PlaneBoundaryCondition_2_2, + PlaneBoundaryCondition_3_3, + PottsBasedCellPopulation_2, + PottsBasedCellPopulation_3, + PythonSimulationModifier_2, + PythonSimulationModifier_3, + RadialCellDataDistributionWriter_2_2, + RadialCellDataDistributionWriter_3_3, + RandomCaSwitchingUpdateRule_2, + RandomCaSwitchingUpdateRule_3, + RandomCellKiller_2, + RandomCellKiller_3, + RandomDirectionCentreBasedDivisionRule_2_2, + RandomDirectionCentreBasedDivisionRule_3_3, + RandomDirectionVertexBasedDivisionRule_2, + RandomDirectionVertexBasedDivisionRule_3, + RepulsionForce_2, + RepulsionForce_3, + ShortAxisImmersedBoundaryDivisionRule_2, + ShortAxisImmersedBoundaryDivisionRule_3, + ShortAxisVertexBasedDivisionRule_2, + ShortAxisVertexBasedDivisionRule_3, + ShovingCaBasedDivisionRule_2, + ShovingCaBasedDivisionRule_3, + SimpleOxygenBasedCellCycleModel, + SimpleTargetAreaModifier_2, + SimpleTargetAreaModifier_3, + SimulationTime, + SlidingBoundaryCondition_2, + SlidingBoundaryCondition_3, + SphereGeometryBoundaryCondition_2, + SphereGeometryBoundaryCondition_3, + StemCellProliferativeType, + StochasticOxygenBasedCellCycleModel, + SurfaceAreaConstraintPottsUpdateRule_2, + SurfaceAreaConstraintPottsUpdateRule_3, + T2SwapCellKiller_2, + T2SwapCellKiller_3, + TargetAreaLinearGrowthModifier_2, + TargetAreaLinearGrowthModifier_3, + TargetedCellKiller_2, + TargetedCellKiller_3, + TransitCellProliferativeType, + TysonNovakCellCycleModel, + UniformCellCycleModel, + UniformG1GenerationalCellCycleModel, + VertexBasedCellPopulation_2, + VertexBasedCellPopulation_3, + VertexBasedPopulationSrn_2, + VertexBasedPopulationSrn_3, + VertexIntersectionSwapLocationsWriter_2_2, + VertexIntersectionSwapLocationsWriter_3_3, + VertexT1SwapLocationsWriter_2_2, + VertexT1SwapLocationsWriter_3_3, + VertexT2SwapLocationsWriter_2_2, + VertexT2SwapLocationsWriter_3_3, + VertexT3SwapLocationsWriter_2_2, + VertexT3SwapLocationsWriter_3_3, + VolumeConstraintPottsUpdateRule_2, + VolumeConstraintPottsUpdateRule_3, + VolumeTrackingModifier_2, + VolumeTrackingModifier_3, + VonMisesVertexBasedDivisionRule_2, + VonMisesVertexBasedDivisionRule_3, + VoronoiDataWriter_2_2, + VoronoiDataWriter_3_3, + VtkSceneModifier_2, + VtkSceneModifier_3, + WelikyOsterForce_2, + WelikyOsterForce_3, + WildTypeCellMutationState, +) +from chaste._syntax import DeprecatedClass, TemplateClassDict +from chaste.cell_based._fortests import ( + AbstractCellBasedTestSuite, + AbstractCellBasedWithTimingsTestSuite, + SetupNotebookTest, + TearDownNotebookTest, +) + +# Template Class Syntax +AdhesionPottsUpdateRule = TemplateClassDict( + { + ("2",): AdhesionPottsUpdateRule_2, + ("3",): AdhesionPottsUpdateRule_3, + } +) + +ApoptoticCellKiller = TemplateClassDict( + { + ("2",): ApoptoticCellKiller_2, + ("3",): ApoptoticCellKiller_3, + } +) + +AttractingPlaneBoundaryCondition = TemplateClassDict( + { + ("2",): AttractingPlaneBoundaryCondition_2_2, + ("2", "2"): AttractingPlaneBoundaryCondition_2_2, + ("3",): AttractingPlaneBoundaryCondition_3_3, + ("3", "3"): AttractingPlaneBoundaryCondition_3_3, + } +) + +BoundaryNodeWriter = TemplateClassDict( + { + ("2",): BoundaryNodeWriter_2_2, + ("2", "2"): BoundaryNodeWriter_2_2, + ("3",): BoundaryNodeWriter_3_3, + ("3", "3"): BoundaryNodeWriter_3_3, + } +) + +BuskeAdhesiveForce = TemplateClassDict( + { + ("2",): BuskeAdhesiveForce_2, + ("3",): BuskeAdhesiveForce_3, + } +) + +BuskeCompressionForce = TemplateClassDict( + { + ("2",): BuskeCompressionForce_2, + ("3",): BuskeCompressionForce_3, + } +) + +BuskeElasticForce = TemplateClassDict( + { + ("2",): BuskeElasticForce_2, + ("3",): BuskeElasticForce_3, + } +) + +CaBasedCellPopulation = TemplateClassDict( + { + ("2",): CaBasedCellPopulation_2, + ("3",): CaBasedCellPopulation_3, + } +) + +CellAgesWriter = TemplateClassDict( + { + ("2",): CellAgesWriter_2_2, + ("2", "2"): CellAgesWriter_2_2, + ("3",): CellAgesWriter_3_3, + ("3", "3"): CellAgesWriter_3_3, + } +) + +CellAncestorWriter = TemplateClassDict( + { + ("2",): CellAncestorWriter_2_2, + ("2", "2"): CellAncestorWriter_2_2, + ("3",): CellAncestorWriter_3_3, + ("3", "3"): CellAncestorWriter_3_3, + } +) + +CellAppliedForceWriter = TemplateClassDict( + { + ("2",): CellAppliedForceWriter_2_2, + ("2", "2"): CellAppliedForceWriter_2_2, + ("3",): CellAppliedForceWriter_3_3, + ("3", "3"): CellAppliedForceWriter_3_3, + } +) + +CellCycleModelProteinConcentrationsWriter = TemplateClassDict( + { + ("2",): CellCycleModelProteinConcentrationsWriter_2_2, + ("2", "2"): CellCycleModelProteinConcentrationsWriter_2_2, + ("3",): CellCycleModelProteinConcentrationsWriter_3_3, + ("3", "3"): CellCycleModelProteinConcentrationsWriter_3_3, + } +) + +CellDataItemWriter = TemplateClassDict( + { + ("2",): CellDataItemWriter_2_2, + ("2", "2"): CellDataItemWriter_2_2, + ("3",): CellDataItemWriter_3_3, + ("3", "3"): CellDataItemWriter_3_3, + } +) + +CellDeltaNotchWriter = TemplateClassDict( + { + ("2",): CellDeltaNotchWriter_2_2, + ("2", "2"): CellDeltaNotchWriter_2_2, + ("3",): CellDeltaNotchWriter_3_3, + ("3", "3"): CellDeltaNotchWriter_3_3, + } +) + +CellDivisionLocationsWriter = TemplateClassDict( + { + ("2",): CellDivisionLocationsWriter_2_2, + ("2", "2"): CellDivisionLocationsWriter_2_2, + ("3",): CellDivisionLocationsWriter_3_3, + ("3", "3"): CellDivisionLocationsWriter_3_3, + } +) + +CellIdWriter = TemplateClassDict( + { + ("2",): CellIdWriter_2_2, + ("2", "2"): CellIdWriter_2_2, + ("3",): CellIdWriter_3_3, + ("3", "3"): CellIdWriter_3_3, + } +) + +CellLabelWriter = TemplateClassDict( + { + ("2",): CellLabelWriter_2_2, + ("2", "2"): CellLabelWriter_2_2, + ("3",): CellLabelWriter_3_3, + ("3", "3"): CellLabelWriter_3_3, + } +) + +CellLocationIndexWriter = TemplateClassDict( + { + ("2",): CellLocationIndexWriter_2_2, + ("2", "2"): CellLocationIndexWriter_2_2, + ("3",): CellLocationIndexWriter_3_3, + ("3", "3"): CellLocationIndexWriter_3_3, + } +) + +CellMutationStatesCountWriter = TemplateClassDict( + { + ("2",): CellMutationStatesCountWriter_2_2, + ("2", "2"): CellMutationStatesCountWriter_2_2, + ("3",): CellMutationStatesCountWriter_3_3, + ("3", "3"): CellMutationStatesCountWriter_3_3, + } +) + +CellMutationStatesWriter = TemplateClassDict( + { + ("2",): CellMutationStatesWriter_2_2, + ("2", "2"): CellMutationStatesWriter_2_2, + ("3",): CellMutationStatesWriter_3_3, + ("3", "3"): CellMutationStatesWriter_3_3, + } +) + +CellPopulationAdjacencyMatrixWriter = TemplateClassDict( + { + ("2",): CellPopulationAdjacencyMatrixWriter_2_2, + ("2", "2"): CellPopulationAdjacencyMatrixWriter_2_2, + ("3",): CellPopulationAdjacencyMatrixWriter_3_3, + ("3", "3"): CellPopulationAdjacencyMatrixWriter_3_3, + } +) + +CellPopulationAreaWriter = TemplateClassDict( + { + ("2",): CellPopulationAreaWriter_2_2, + ("2", "2"): CellPopulationAreaWriter_2_2, + ("3",): CellPopulationAreaWriter_3_3, + ("3", "3"): CellPopulationAreaWriter_3_3, + } +) + +CellPopulationElementWriter = TemplateClassDict( + { + ("2",): CellPopulationElementWriter_2_2, + ("2", "2"): CellPopulationElementWriter_2_2, + ("3",): CellPopulationElementWriter_3_3, + ("3", "3"): CellPopulationElementWriter_3_3, + } +) + +CellProliferativePhasesCountWriter = TemplateClassDict( + { + ("2",): CellProliferativePhasesCountWriter_2_2, + ("2", "2"): CellProliferativePhasesCountWriter_2_2, + ("3",): CellProliferativePhasesCountWriter_3_3, + ("3", "3"): CellProliferativePhasesCountWriter_3_3, + } +) + +CellProliferativePhasesWriter = TemplateClassDict( + { + ("2",): CellProliferativePhasesWriter_2_2, + ("2", "2"): CellProliferativePhasesWriter_2_2, + ("3",): CellProliferativePhasesWriter_3_3, + ("3", "3"): CellProliferativePhasesWriter_3_3, + } +) + +CellProliferativeTypesCountWriter = TemplateClassDict( + { + ("2",): CellProliferativeTypesCountWriter_2_2, + ("2", "2"): CellProliferativeTypesCountWriter_2_2, + ("3",): CellProliferativeTypesCountWriter_3_3, + ("3", "3"): CellProliferativeTypesCountWriter_3_3, + } +) + +CellProliferativeTypesWriter = TemplateClassDict( + { + ("2",): CellProliferativeTypesWriter_2_2, + ("2", "2"): CellProliferativeTypesWriter_2_2, + ("3",): CellProliferativeTypesWriter_3_3, + ("3", "3"): CellProliferativeTypesWriter_3_3, + } +) + +CellRadiusWriter = TemplateClassDict( + { + ("2",): CellRadiusWriter_2_2, + ("2", "2"): CellRadiusWriter_2_2, + ("3",): CellRadiusWriter_3_3, + ("3", "3"): CellRadiusWriter_3_3, + } +) + +CellRemovalLocationsWriter = TemplateClassDict( + { + ("2",): CellRemovalLocationsWriter_2_2, + ("2", "2"): CellRemovalLocationsWriter_2_2, + ("3",): CellRemovalLocationsWriter_3_3, + ("3", "3"): CellRemovalLocationsWriter_3_3, + } +) + +CellRosetteRankWriter = TemplateClassDict( + { + ("2",): CellRosetteRankWriter_2_2, + ("2", "2"): CellRosetteRankWriter_2_2, + ("3",): CellRosetteRankWriter_3_3, + ("3", "3"): CellRosetteRankWriter_3_3, + } +) + +CellsGenerator = TemplateClassDict( + { + ( + "Alarcon2004OxygenBasedCellCycleModel", + "2", + ): CellsGenerator_Alarcon2004OxygenBasedCellCycleModel_2, + ( + "Alarcon2004OxygenBasedCellCycleModel", + "3", + ): CellsGenerator_Alarcon2004OxygenBasedCellCycleModel_3, + ( + "AlwaysDivideCellCycleModel", + "2", + ): CellsGenerator_AlwaysDivideCellCycleModel_2, + ( + "AlwaysDivideCellCycleModel", + "3", + ): CellsGenerator_AlwaysDivideCellCycleModel_3, + ( + "BernoulliTrialCellCycleModel", + "2", + ): CellsGenerator_BernoulliTrialCellCycleModel_2, + ( + "BernoulliTrialCellCycleModel", + "3", + ): CellsGenerator_BernoulliTrialCellCycleModel_3, + ( + "BiasedBernoulliTrialCellCycleModel", + "2", + ): CellsGenerator_BiasedBernoulliTrialCellCycleModel_2, + ( + "BiasedBernoulliTrialCellCycleModel", + "3", + ): CellsGenerator_BiasedBernoulliTrialCellCycleModel_3, + ( + "ContactInhibitionCellCycleModel", + "2", + ): CellsGenerator_ContactInhibitionCellCycleModel_2, + ( + "ContactInhibitionCellCycleModel", + "3", + ): CellsGenerator_ContactInhibitionCellCycleModel_3, + ( + "ExponentialG1GenerationalCellCycleModel", + "2", + ): CellsGenerator_ExponentialG1GenerationalCellCycleModel_2, + ( + "ExponentialG1GenerationalCellCycleModel", + "3", + ): CellsGenerator_ExponentialG1GenerationalCellCycleModel_3, + ( + "FixedG1GenerationalCellCycleModel", + "2", + ): CellsGenerator_FixedG1GenerationalCellCycleModel_2, + ( + "FixedG1GenerationalCellCycleModel", + "3", + ): CellsGenerator_FixedG1GenerationalCellCycleModel_3, + ( + "FixedSequenceCellCycleModel", + "2", + ): CellsGenerator_FixedSequenceCellCycleModel_2, + ( + "FixedSequenceCellCycleModel", + "3", + ): CellsGenerator_FixedSequenceCellCycleModel_3, + ("GammaG1CellCycleModel", "2"): CellsGenerator_GammaG1CellCycleModel_2, + ("GammaG1CellCycleModel", "3"): CellsGenerator_GammaG1CellCycleModel_3, + ( + "LabelDependentBernoulliTrialCellCycleModel", + "2", + ): CellsGenerator_LabelDependentBernoulliTrialCellCycleModel_2, + ( + "LabelDependentBernoulliTrialCellCycleModel", + "3", + ): CellsGenerator_LabelDependentBernoulliTrialCellCycleModel_3, + ("NoCellCycleModel", "2"): CellsGenerator_NoCellCycleModel_2, + ("NoCellCycleModel", "3"): CellsGenerator_NoCellCycleModel_3, + ( + "SimpleOxygenBasedCellCycleModel", + "2", + ): CellsGenerator_SimpleOxygenBasedCellCycleModel_2, + ( + "SimpleOxygenBasedCellCycleModel", + "3", + ): CellsGenerator_SimpleOxygenBasedCellCycleModel_3, + ( + "StochasticOxygenBasedCellCycleModel", + "2", + ): CellsGenerator_StochasticOxygenBasedCellCycleModel_2, + ( + "StochasticOxygenBasedCellCycleModel", + "3", + ): CellsGenerator_StochasticOxygenBasedCellCycleModel_3, + ( + "TysonNovakCellCycleModel", + "2", + ): CellsGenerator_TysonNovakCellCycleModel_2, + ( + "TysonNovakCellCycleModel", + "3", + ): CellsGenerator_TysonNovakCellCycleModel_3, + ("UniformCellCycleModel", "2"): CellsGenerator_UniformCellCycleModel_2, + ("UniformCellCycleModel", "3"): CellsGenerator_UniformCellCycleModel_3, + ( + "UniformG1GenerationalCellCycleModel", + "2", + ): CellsGenerator_UniformG1GenerationalCellCycleModel_2, + ( + "UniformG1GenerationalCellCycleModel", + "3", + ): CellsGenerator_UniformG1GenerationalCellCycleModel_3, + } +) + +CellVolumesWriter = TemplateClassDict( + { + ("2",): CellVolumesWriter_2_2, + ("2", "2"): CellVolumesWriter_2_2, + ("3",): CellVolumesWriter_3_3, + ("3", "3"): CellVolumesWriter_3_3, + } +) + +ChemotacticForce = TemplateClassDict( + { + ("2",): ChemotacticForce_2, + ("3",): ChemotacticForce_3, + } +) + +ChemotaxisPottsUpdateRule = TemplateClassDict( + { + ("2",): ChemotaxisPottsUpdateRule_2, + ("3",): ChemotaxisPottsUpdateRule_3, + } +) + +DeltaNotchEdgeInteriorTrackingModifier = TemplateClassDict( + { + ("2",): DeltaNotchEdgeInteriorTrackingModifier_2, + ("3",): DeltaNotchEdgeInteriorTrackingModifier_3, + } +) + +DeltaNotchTrackingModifier = TemplateClassDict( + { + ("2",): DeltaNotchTrackingModifier_2, + ("3",): DeltaNotchTrackingModifier_3, + } +) + +DeltaNotchEdgeTrackingModifier = TemplateClassDict( + { + ("2",): DeltaNotchEdgeTrackingModifier_2, + ("3",): DeltaNotchEdgeTrackingModifier_3, + } +) + +DifferentialAdhesionGeneralisedLinearSpringForce = TemplateClassDict( + { + ("2",): DifferentialAdhesionGeneralisedLinearSpringForce_2_2, + ("2", "2"): DifferentialAdhesionGeneralisedLinearSpringForce_2_2, + ("3",): DifferentialAdhesionGeneralisedLinearSpringForce_3_3, + ("3", "3"): DifferentialAdhesionGeneralisedLinearSpringForce_3_3, + } +) + +DifferentialAdhesionPottsUpdateRule = TemplateClassDict( + { + ("2",): DifferentialAdhesionPottsUpdateRule_2, + ("3",): DifferentialAdhesionPottsUpdateRule_3, + } +) + +DiffusionCaUpdateRule = TemplateClassDict( + { + ("2",): DiffusionCaUpdateRule_2, + ("3",): DiffusionCaUpdateRule_3, + } +) + +DiffusionForce = TemplateClassDict( + { + ("2",): DiffusionForce_2, + ("3",): DiffusionForce_3, + } +) + +DivisionBiasTrackingModifier = TemplateClassDict( + { + ("2",): DivisionBiasTrackingModifier_2, + ("3",): DivisionBiasTrackingModifier_3, + } +) + +ExclusionCaBasedDivisionRule = TemplateClassDict( + { + ("2",): ExclusionCaBasedDivisionRule_2, + ("3",): ExclusionCaBasedDivisionRule_3, + } +) + +ExtrinsicPullModifier = TemplateClassDict( + { + ("2",): ExtrinsicPullModifier_2, + ("3",): ExtrinsicPullModifier_3, + } +) + +FarhadifarForce = TemplateClassDict( + { + ("2",): FarhadifarForce_2, + ("3",): FarhadifarForce_3, + } +) + +FixedCentreBasedDivisionRule = TemplateClassDict( + { + ("2",): FixedCentreBasedDivisionRule_2_2, + ("2", "2"): FixedCentreBasedDivisionRule_2_2, + ("3",): FixedCentreBasedDivisionRule_3_3, + ("3", "3"): FixedCentreBasedDivisionRule_3_3, + } +) + +FixedVertexBasedDivisionRule = TemplateClassDict( + { + ("2",): FixedVertexBasedDivisionRule_2, + ("3",): FixedVertexBasedDivisionRule_3, + } +) + +ForwardEulerNumericalMethod = TemplateClassDict( + { + ("2",): ForwardEulerNumericalMethod_2_2, + ("2", "2"): ForwardEulerNumericalMethod_2_2, + ("3",): ForwardEulerNumericalMethod_3_3, + ("3", "3"): ForwardEulerNumericalMethod_3_3, + } +) + +GeneralisedLinearSpringForce = TemplateClassDict( + { + ("2",): GeneralisedLinearSpringForce_2_2, + ("2", "2"): GeneralisedLinearSpringForce_2_2, + ("3",): GeneralisedLinearSpringForce_3_3, + ("3", "3"): GeneralisedLinearSpringForce_3_3, + } +) + +HeterotypicBoundaryLengthWriter = TemplateClassDict( + { + ("2",): HeterotypicBoundaryLengthWriter_2_2, + ("2", "2"): HeterotypicBoundaryLengthWriter_2_2, + ("3",): HeterotypicBoundaryLengthWriter_3_3, + ("3", "3"): HeterotypicBoundaryLengthWriter_3_3, + } +) + +ImmersedBoundaryBoundaryCellWriter = TemplateClassDict( + { + ("2",): ImmersedBoundaryBoundaryCellWriter_2_2, + ("2", "2"): ImmersedBoundaryBoundaryCellWriter_2_2, + ("3",): ImmersedBoundaryBoundaryCellWriter_3_3, + ("3", "3"): ImmersedBoundaryBoundaryCellWriter_3_3, + } +) + +ImmersedBoundaryCellPopulation = TemplateClassDict( + { + ("2",): ImmersedBoundaryCellPopulation_2, + ("3",): ImmersedBoundaryCellPopulation_3, + } +) + +ImmersedBoundaryKinematicFeedbackForce = TemplateClassDict( + { + ("2",): ImmersedBoundaryKinematicFeedbackForce_2, + ("3",): ImmersedBoundaryKinematicFeedbackForce_3, + } +) + +ImmersedBoundaryLinearDifferentialAdhesionForce = TemplateClassDict( + { + ("2",): ImmersedBoundaryLinearDifferentialAdhesionForce_2, + ("3",): ImmersedBoundaryLinearDifferentialAdhesionForce_3, + } +) + +ImmersedBoundaryLinearInteractionForce = TemplateClassDict( + { + ("2",): ImmersedBoundaryLinearInteractionForce_2, + ("3",): ImmersedBoundaryLinearInteractionForce_3, + } +) + +ImmersedBoundaryLinearMembraneForce = TemplateClassDict( + { + ("2",): ImmersedBoundaryLinearMembraneForce_2, + ("3",): ImmersedBoundaryLinearMembraneForce_3, + } +) + +ImmersedBoundaryMorseInteractionForce = TemplateClassDict( + { + ("2",): ImmersedBoundaryMorseInteractionForce_2, + ("3",): ImmersedBoundaryMorseInteractionForce_3, + } +) + +ImmersedBoundaryMorseMembraneForce = TemplateClassDict( + { + ("2",): ImmersedBoundaryMorseMembraneForce_2, + ("3",): ImmersedBoundaryMorseMembraneForce_3, + } +) + +ImmersedBoundaryNeighbourNumberWriter = TemplateClassDict( + { + ("2",): ImmersedBoundaryNeighbourNumberWriter_2_2, + ("2", "2"): ImmersedBoundaryNeighbourNumberWriter_2_2, + ("3",): ImmersedBoundaryNeighbourNumberWriter_3_3, + ("3", "3"): ImmersedBoundaryNeighbourNumberWriter_3_3, + } +) + +ImmersedBoundarySimulationModifier = TemplateClassDict( + { + ("2",): ImmersedBoundarySimulationModifier_2, + ("3",): ImmersedBoundarySimulationModifier_3, + } +) + +ImmersedBoundarySvgWriter = TemplateClassDict( + { + ("2",): ImmersedBoundarySvgWriter_2, + ("3",): ImmersedBoundarySvgWriter_3, + } +) + +IsolatedLabelledCellKiller = TemplateClassDict( + { + ("2",): IsolatedLabelledCellKiller_2, + ("3",): IsolatedLabelledCellKiller_3, + } +) + +LegacyCellProliferativeTypesWriter = TemplateClassDict( + { + ("2",): LegacyCellProliferativeTypesWriter_2_2, + ("2", "2"): LegacyCellProliferativeTypesWriter_2_2, + ("3",): LegacyCellProliferativeTypesWriter_3_3, + ("3", "3"): LegacyCellProliferativeTypesWriter_3_3, + } +) + +MeshBasedCellPopulation = TemplateClassDict( + { + ("2",): MeshBasedCellPopulation_2_2, + ("2", "2"): MeshBasedCellPopulation_2_2, + ("3",): MeshBasedCellPopulation_3_3, + ("3", "3"): MeshBasedCellPopulation_3_3, + } +) + +MeshBasedCellPopulationWithGhostNodes = TemplateClassDict( + { + ("2",): MeshBasedCellPopulationWithGhostNodes_2, + ("3",): MeshBasedCellPopulationWithGhostNodes_3, + } +) + +NagaiHondaDifferentialAdhesionForce = TemplateClassDict( + { + ("2",): NagaiHondaDifferentialAdhesionForce_2, + ("3",): NagaiHondaDifferentialAdhesionForce_3, + } +) + +NagaiHondaForce = TemplateClassDict( + { + ("2",): NagaiHondaForce_2, + ("3",): NagaiHondaForce_3, + } +) + +NodeBasedCellPopulation = TemplateClassDict( + { + ("2",): NodeBasedCellPopulation_2, + ("3",): NodeBasedCellPopulation_3, + } +) + +NodeBasedCellPopulationWithBuskeUpdate = TemplateClassDict( + { + ("2",): NodeBasedCellPopulationWithBuskeUpdate_2, + ("3",): NodeBasedCellPopulationWithBuskeUpdate_3, + } +) + +NodeBasedCellPopulationWithParticles = TemplateClassDict( + { + ("2",): NodeBasedCellPopulationWithParticles_2, + ("3",): NodeBasedCellPopulationWithParticles_3, + } +) + +NodeLocationWriter = TemplateClassDict( + { + ("2",): NodeLocationWriter_2_2, + ("2", "2"): NodeLocationWriter_2_2, + ("3",): NodeLocationWriter_3_3, + ("3", "3"): NodeLocationWriter_3_3, + } +) + +NodeVelocityWriter = TemplateClassDict( + { + ("2",): NodeVelocityWriter_2_2, + ("2", "2"): NodeVelocityWriter_2_2, + ("3",): NodeVelocityWriter_3_3, + ("3", "3"): NodeVelocityWriter_3_3, + } +) + +NormallyDistributedTargetAreaModifier = TemplateClassDict( + { + ("2",): NormallyDistributedTargetAreaModifier_2, + ("3",): NormallyDistributedTargetAreaModifier_3, + } +) + +OffLatticeSimulation = TemplateClassDict( + { + ("2",): OffLatticeSimulation_2_2, + ("2", "2"): OffLatticeSimulation_2_2, + ("3",): OffLatticeSimulation_3_3, + ("3", "3"): OffLatticeSimulation_3_3, + } +) + +OnLatticeSimulation = TemplateClassDict( + { + ("2",): OnLatticeSimulation_2, + ("3",): OnLatticeSimulation_3, + } +) + +PlanarPolarisedFarhadifarForce = TemplateClassDict( + { + ("2",): PlanarPolarisedFarhadifarForce_2, + ("3",): PlanarPolarisedFarhadifarForce_3, + } +) + +PlaneBasedCellKiller = TemplateClassDict( + { + ("2",): PlaneBasedCellKiller_2, + ("3",): PlaneBasedCellKiller_3, + } +) + +PlaneBoundaryCondition = TemplateClassDict( + { + ("2",): PlaneBoundaryCondition_2_2, + ("2", "2"): PlaneBoundaryCondition_2_2, + ("3",): PlaneBoundaryCondition_3_3, + ("3", "3"): PlaneBoundaryCondition_3_3, + } +) + +PottsBasedCellPopulation = TemplateClassDict( + { + ("2",): PottsBasedCellPopulation_2, + ("3",): PottsBasedCellPopulation_3, + } +) + +PythonSimulationModifier = TemplateClassDict( + { + ("2",): PythonSimulationModifier_2, + ("3",): PythonSimulationModifier_3, + } +) + +RadialCellDataDistributionWriter = TemplateClassDict( + { + ("2",): RadialCellDataDistributionWriter_2_2, + ("2", "2"): RadialCellDataDistributionWriter_2_2, + ("3",): RadialCellDataDistributionWriter_3_3, + ("3", "3"): RadialCellDataDistributionWriter_3_3, + } +) + +RandomCaSwitchingUpdateRule = TemplateClassDict( + { + ("2",): RandomCaSwitchingUpdateRule_2, + ("3",): RandomCaSwitchingUpdateRule_3, + } +) + +RandomCellKiller = TemplateClassDict( + { + ("2",): RandomCellKiller_2, + ("3",): RandomCellKiller_3, + } +) + +RandomDirectionCentreBasedDivisionRule = TemplateClassDict( + { + ("2",): RandomDirectionCentreBasedDivisionRule_2_2, + ("2", "2"): RandomDirectionCentreBasedDivisionRule_2_2, + ("3",): RandomDirectionCentreBasedDivisionRule_3_3, + ("3", "3"): RandomDirectionCentreBasedDivisionRule_3_3, + } +) + +RandomDirectionVertexBasedDivisionRule = TemplateClassDict( + { + ("2",): RandomDirectionVertexBasedDivisionRule_2, + ("3",): RandomDirectionVertexBasedDivisionRule_3, + } +) + +RepulsionForce = TemplateClassDict( + { + ("2",): RepulsionForce_2, + ("3",): RepulsionForce_3, + } +) + +ShortAxisImmersedBoundaryDivisionRule = TemplateClassDict( + { + ("2",): ShortAxisImmersedBoundaryDivisionRule_2, + ("3",): ShortAxisImmersedBoundaryDivisionRule_3, + } +) + +ShortAxisVertexBasedDivisionRule = TemplateClassDict( + { + ("2",): ShortAxisVertexBasedDivisionRule_2, + ("3",): ShortAxisVertexBasedDivisionRule_3, + } +) + +ShovingCaBasedDivisionRule = TemplateClassDict( + { + ("2",): ShovingCaBasedDivisionRule_2, + ("3",): ShovingCaBasedDivisionRule_3, + } +) + +SimpleTargetAreaModifier = TemplateClassDict( + { + ("2",): SimpleTargetAreaModifier_2, + ("3",): SimpleTargetAreaModifier_3, + } +) + +SlidingBoundaryCondition = TemplateClassDict( + { + ("2",): SlidingBoundaryCondition_2, + ("3",): SlidingBoundaryCondition_3, + } +) + +SphereGeometryBoundaryCondition = TemplateClassDict( + { + ("2",): SphereGeometryBoundaryCondition_2, + ("3",): SphereGeometryBoundaryCondition_3, + } +) + +SurfaceAreaConstraintPottsUpdateRule = TemplateClassDict( + { + ("2",): SurfaceAreaConstraintPottsUpdateRule_2, + ("3",): SurfaceAreaConstraintPottsUpdateRule_3, + } +) + +T2SwapCellKiller = TemplateClassDict( + { + ("2",): T2SwapCellKiller_2, + ("3",): T2SwapCellKiller_3, + } +) + +TargetAreaLinearGrowthModifier = TemplateClassDict( + { + ("2",): TargetAreaLinearGrowthModifier_2, + ("3",): TargetAreaLinearGrowthModifier_3, + } +) + +TargetedCellKiller = TemplateClassDict( + { + ("2",): TargetedCellKiller_2, + ("3",): TargetedCellKiller_3, + } +) + +VertexBasedCellPopulation = TemplateClassDict( + { + ("2",): VertexBasedCellPopulation_2, + ("3",): VertexBasedCellPopulation_3, + } +) + +VertexBasedPopulationSrn = TemplateClassDict( + { + ("2",): VertexBasedPopulationSrn_2, + ("3",): VertexBasedPopulationSrn_3, + } +) + +VertexIntersectionSwapLocationsWriter = TemplateClassDict( + { + ("2",): VertexIntersectionSwapLocationsWriter_2_2, + ("2", "2"): VertexIntersectionSwapLocationsWriter_2_2, + ("3",): VertexIntersectionSwapLocationsWriter_3_3, + ("3", "3"): VertexIntersectionSwapLocationsWriter_3_3, + } +) + +VertexT1SwapLocationsWriter = TemplateClassDict( + { + ("2",): VertexT1SwapLocationsWriter_2_2, + ("2", "2"): VertexT1SwapLocationsWriter_2_2, + ("3",): VertexT1SwapLocationsWriter_3_3, + ("3", "3"): VertexT1SwapLocationsWriter_3_3, + } +) + +VertexT2SwapLocationsWriter = TemplateClassDict( + { + ("2",): VertexT2SwapLocationsWriter_2_2, + ("2", "2"): VertexT2SwapLocationsWriter_2_2, + ("3",): VertexT2SwapLocationsWriter_3_3, + ("3", "3"): VertexT2SwapLocationsWriter_3_3, + } +) + +VertexT3SwapLocationsWriter = TemplateClassDict( + { + ("2",): VertexT3SwapLocationsWriter_2_2, + ("2", "2"): VertexT3SwapLocationsWriter_2_2, + ("3",): VertexT3SwapLocationsWriter_3_3, + ("3", "3"): VertexT3SwapLocationsWriter_3_3, + } +) + +VolumeConstraintPottsUpdateRule = TemplateClassDict( + { + ("2",): VolumeConstraintPottsUpdateRule_2, + ("3",): VolumeConstraintPottsUpdateRule_3, + } +) + +VolumeTrackingModifier = TemplateClassDict( + { + ("2",): VolumeTrackingModifier_2, + ("3",): VolumeTrackingModifier_3, + } +) + +VonMisesVertexBasedDivisionRule = TemplateClassDict( + { + ("2",): VonMisesVertexBasedDivisionRule_2, + ("3",): VonMisesVertexBasedDivisionRule_3, + } +) + +VoronoiDataWriter = TemplateClassDict( + { + ("2",): VoronoiDataWriter_2_2, + ("2", "2"): VoronoiDataWriter_2_2, + ("3",): VoronoiDataWriter_3_3, + ("3", "3"): VoronoiDataWriter_3_3, + } +) + +VtkSceneModifier = TemplateClassDict( + { + ("2",): VtkSceneModifier_2, + ("3",): VtkSceneModifier_3, + } +) + +WelikyOsterForce = TemplateClassDict( + { + ("2",): WelikyOsterForce_2, + ("3",): WelikyOsterForce_3, + } +) + +# Deprecated Class Syntax +AdhesionPottsUpdateRule2 = DeprecatedClass("AdhesionPottsUpdateRule2", AdhesionPottsUpdateRule_2) +AdhesionPottsUpdateRule3 = DeprecatedClass("AdhesionPottsUpdateRule3", AdhesionPottsUpdateRule_3) +ApoptoticCellKiller2 = DeprecatedClass("ApoptoticCellKiller2", ApoptoticCellKiller_2) +ApoptoticCellKiller3 = DeprecatedClass("ApoptoticCellKiller3", ApoptoticCellKiller_3) +AttractingPlaneBoundaryCondition2_2 = DeprecatedClass("AttractingPlaneBoundaryCondition2_2", AttractingPlaneBoundaryCondition_2_2) +AttractingPlaneBoundaryCondition3_3 = DeprecatedClass("AttractingPlaneBoundaryCondition3_3", AttractingPlaneBoundaryCondition_3_3) +BoundaryNodeWriter2_2 = DeprecatedClass("BoundaryNodeWriter2_2", BoundaryNodeWriter_2_2) +BoundaryNodeWriter3_3 = DeprecatedClass("BoundaryNodeWriter3_3", BoundaryNodeWriter_3_3) +BuskeAdhesiveForce2 = DeprecatedClass("BuskeAdhesiveForce2", BuskeAdhesiveForce_2) +BuskeAdhesiveForce3 = DeprecatedClass("BuskeAdhesiveForce3", BuskeAdhesiveForce_3) +BuskeCompressionForce2 = DeprecatedClass("BuskeCompressionForce2", BuskeCompressionForce_2) +BuskeCompressionForce3 = DeprecatedClass("BuskeCompressionForce3", BuskeCompressionForce_3) +BuskeElasticForce2 = DeprecatedClass("BuskeElasticForce2", BuskeElasticForce_2) +BuskeElasticForce3 = DeprecatedClass("BuskeElasticForce3", BuskeElasticForce_3) +CaBasedCellPopulation2 = DeprecatedClass("CaBasedCellPopulation2", CaBasedCellPopulation_2) +CaBasedCellPopulation3 = DeprecatedClass("CaBasedCellPopulation3", CaBasedCellPopulation_3) +CellAgesWriter2_2 = DeprecatedClass("CellAgesWriter2_2", CellAgesWriter_2_2) +CellAgesWriter3_3 = DeprecatedClass("CellAgesWriter3_3", CellAgesWriter_3_3) +CellAncestorWriter2_2 = DeprecatedClass("CellAncestorWriter2_2", CellAncestorWriter_2_2) +CellAncestorWriter3_3 = DeprecatedClass("CellAncestorWriter3_3", CellAncestorWriter_3_3) +CellAppliedForceWriter2_2 = DeprecatedClass("CellAppliedForceWriter2_2", CellAppliedForceWriter_2_2) +CellAppliedForceWriter3_3 = DeprecatedClass("CellAppliedForceWriter3_3", CellAppliedForceWriter_3_3) +CellCycleModelProteinConcentrationsWriter2_2 = DeprecatedClass("CellCycleModelProteinConcentrationsWriter2_2", CellCycleModelProteinConcentrationsWriter_2_2) +CellCycleModelProteinConcentrationsWriter3_3 = DeprecatedClass("CellCycleModelProteinConcentrationsWriter3_3", CellCycleModelProteinConcentrationsWriter_3_3) +CellDataItemWriter2_2 = DeprecatedClass("CellDataItemWriter2_2", CellDataItemWriter_2_2) +CellDataItemWriter3_3 = DeprecatedClass("CellDataItemWriter3_3", CellDataItemWriter_3_3) +CellDeltaNotchWriter2_2 = DeprecatedClass("CellDeltaNotchWriter2_2", CellDeltaNotchWriter_2_2) +CellDeltaNotchWriter3_3 = DeprecatedClass("CellDeltaNotchWriter3_3", CellDeltaNotchWriter_3_3) +CellDivisionLocationsWriter2_2 = DeprecatedClass("CellDivisionLocationsWriter2_2", CellDivisionLocationsWriter_2_2) +CellDivisionLocationsWriter3_3 = DeprecatedClass("CellDivisionLocationsWriter3_3", CellDivisionLocationsWriter_3_3) +CellIdWriter2_2 = DeprecatedClass("CellIdWriter2_2", CellIdWriter_2_2) +CellIdWriter3_3 = DeprecatedClass("CellIdWriter3_3", CellIdWriter_3_3) +CellLabelWriter2_2 = DeprecatedClass("CellLabelWriter2_2", CellLabelWriter_2_2) +CellLabelWriter3_3 = DeprecatedClass("CellLabelWriter3_3", CellLabelWriter_3_3) +CellLocationIndexWriter2_2 = DeprecatedClass("CellLocationIndexWriter2_2", CellLocationIndexWriter_2_2) +CellLocationIndexWriter3_3 = DeprecatedClass("CellLocationIndexWriter3_3", CellLocationIndexWriter_3_3) +CellMutationStatesCountWriter2_2 = DeprecatedClass("CellMutationStatesCountWriter2_2", CellMutationStatesCountWriter_2_2) +CellMutationStatesCountWriter3_3 = DeprecatedClass("CellMutationStatesCountWriter3_3", CellMutationStatesCountWriter_3_3) +CellMutationStatesWriter2_2 = DeprecatedClass("CellMutationStatesWriter2_2", CellMutationStatesWriter_2_2) +CellMutationStatesWriter3_3 = DeprecatedClass("CellMutationStatesWriter3_3", CellMutationStatesWriter_3_3) +CellPopulationAdjacencyMatrixWriter2_2 = DeprecatedClass("CellPopulationAdjacencyMatrixWriter2_2", CellPopulationAdjacencyMatrixWriter_2_2) +CellPopulationAdjacencyMatrixWriter3_3 = DeprecatedClass("CellPopulationAdjacencyMatrixWriter3_3", CellPopulationAdjacencyMatrixWriter_3_3) +CellPopulationAreaWriter2_2 = DeprecatedClass("CellPopulationAreaWriter2_2", CellPopulationAreaWriter_2_2) +CellPopulationAreaWriter3_3 = DeprecatedClass("CellPopulationAreaWriter3_3", CellPopulationAreaWriter_3_3) +CellPopulationElementWriter2_2 = DeprecatedClass("CellPopulationElementWriter2_2", CellPopulationElementWriter_2_2) +CellPopulationElementWriter3_3 = DeprecatedClass("CellPopulationElementWriter3_3", CellPopulationElementWriter_3_3) +CellProliferativePhasesCountWriter2_2 = DeprecatedClass("CellProliferativePhasesCountWriter2_2", CellProliferativePhasesCountWriter_2_2) +CellProliferativePhasesCountWriter3_3 = DeprecatedClass("CellProliferativePhasesCountWriter3_3", CellProliferativePhasesCountWriter_3_3) +CellProliferativePhasesWriter2_2 = DeprecatedClass("CellProliferativePhasesWriter2_2", CellProliferativePhasesWriter_2_2) +CellProliferativePhasesWriter3_3 = DeprecatedClass("CellProliferativePhasesWriter3_3", CellProliferativePhasesWriter_3_3) +CellProliferativeTypesCountWriter2_2 = DeprecatedClass("CellProliferativeTypesCountWriter2_2", CellProliferativeTypesCountWriter_2_2) +CellProliferativeTypesCountWriter3_3 = DeprecatedClass("CellProliferativeTypesCountWriter3_3", CellProliferativeTypesCountWriter_3_3) +CellProliferativeTypesWriter2_2 = DeprecatedClass("CellProliferativeTypesWriter2_2", CellProliferativeTypesWriter_2_2) +CellProliferativeTypesWriter3_3 = DeprecatedClass("CellProliferativeTypesWriter3_3", CellProliferativeTypesWriter_3_3) +CellRadiusWriter2_2 = DeprecatedClass("CellRadiusWriter2_2", CellRadiusWriter_2_2) +CellRadiusWriter3_3 = DeprecatedClass("CellRadiusWriter3_3", CellRadiusWriter_3_3) +CellRemovalLocationsWriter2_2 = DeprecatedClass("CellRemovalLocationsWriter2_2", CellRemovalLocationsWriter_2_2) +CellRemovalLocationsWriter3_3 = DeprecatedClass("CellRemovalLocationsWriter3_3", CellRemovalLocationsWriter_3_3) +CellRosetteRankWriter2_2 = DeprecatedClass("CellRosetteRankWriter2_2", CellRosetteRankWriter_2_2) +CellRosetteRankWriter3_3 = DeprecatedClass("CellRosetteRankWriter3_3", CellRosetteRankWriter_3_3) +CellsGeneratorAlarcon2004OxygenBasedCellCycleModel_2 = DeprecatedClass("CellsGeneratorAlarcon2004OxygenBasedCellCycleModel_2", CellsGenerator_Alarcon2004OxygenBasedCellCycleModel_2) +CellsGeneratorAlarcon2004OxygenBasedCellCycleModel_3 = DeprecatedClass("CellsGeneratorAlarcon2004OxygenBasedCellCycleModel_3", CellsGenerator_Alarcon2004OxygenBasedCellCycleModel_3) +CellsGeneratorAlwaysDivideCellCycleModel_2 = DeprecatedClass("CellsGeneratorAlwaysDivideCellCycleModel_2", CellsGenerator_AlwaysDivideCellCycleModel_2) +CellsGeneratorAlwaysDivideCellCycleModel_3 = DeprecatedClass("CellsGeneratorAlwaysDivideCellCycleModel_3", CellsGenerator_AlwaysDivideCellCycleModel_3) +CellsGeneratorBernoulliTrialCellCycleModel_2 = DeprecatedClass("CellsGeneratorBernoulliTrialCellCycleModel_2", CellsGenerator_BernoulliTrialCellCycleModel_2) +CellsGeneratorBernoulliTrialCellCycleModel_3 = DeprecatedClass("CellsGeneratorBernoulliTrialCellCycleModel_3", CellsGenerator_BernoulliTrialCellCycleModel_3) +CellsGeneratorBiasedBernoulliTrialCellCycleModel_2 = DeprecatedClass("CellsGeneratorBiasedBernoulliTrialCellCycleModel_2", CellsGenerator_BiasedBernoulliTrialCellCycleModel_2) +CellsGeneratorBiasedBernoulliTrialCellCycleModel_3 = DeprecatedClass("CellsGeneratorBiasedBernoulliTrialCellCycleModel_3", CellsGenerator_BiasedBernoulliTrialCellCycleModel_3) +CellsGeneratorContactInhibitionCellCycleModel_2 = DeprecatedClass("CellsGeneratorContactInhibitionCellCycleModel_2", CellsGenerator_ContactInhibitionCellCycleModel_2) +CellsGeneratorContactInhibitionCellCycleModel_3 = DeprecatedClass("CellsGeneratorContactInhibitionCellCycleModel_3", CellsGenerator_ContactInhibitionCellCycleModel_3) +CellsGeneratorExponentialG1GenerationalCellCycleModel_2 = DeprecatedClass("CellsGeneratorExponentialG1GenerationalCellCycleModel_2", CellsGenerator_ExponentialG1GenerationalCellCycleModel_2) +CellsGeneratorExponentialG1GenerationalCellCycleModel_3 = DeprecatedClass("CellsGeneratorExponentialG1GenerationalCellCycleModel_3", CellsGenerator_ExponentialG1GenerationalCellCycleModel_3) +CellsGeneratorFixedG1GenerationalCellCycleModel_2 = DeprecatedClass("CellsGeneratorFixedG1GenerationalCellCycleModel_2", CellsGenerator_FixedG1GenerationalCellCycleModel_2) +CellsGeneratorFixedG1GenerationalCellCycleModel_3 = DeprecatedClass("CellsGeneratorFixedG1GenerationalCellCycleModel_3", CellsGenerator_FixedG1GenerationalCellCycleModel_3) +CellsGeneratorFixedSequenceCellCycleModel_2 = DeprecatedClass("CellsGeneratorFixedSequenceCellCycleModel_2", CellsGenerator_FixedSequenceCellCycleModel_2) +CellsGeneratorFixedSequenceCellCycleModel_3 = DeprecatedClass("CellsGeneratorFixedSequenceCellCycleModel_3", CellsGenerator_FixedSequenceCellCycleModel_3) +CellsGeneratorGammaG1CellCycleModel_2 = DeprecatedClass("CellsGeneratorGammaG1CellCycleModel_2", CellsGenerator_GammaG1CellCycleModel_2) +CellsGeneratorGammaG1CellCycleModel_3 = DeprecatedClass("CellsGeneratorGammaG1CellCycleModel_3", CellsGenerator_GammaG1CellCycleModel_3) +CellsGeneratorLabelDependentBernoulliTrialCellCycleModel_2 = DeprecatedClass("CellsGeneratorLabelDependentBernoulliTrialCellCycleModel_2", CellsGenerator_LabelDependentBernoulliTrialCellCycleModel_2) +CellsGeneratorLabelDependentBernoulliTrialCellCycleModel_3 = DeprecatedClass("CellsGeneratorLabelDependentBernoulliTrialCellCycleModel_3", CellsGenerator_LabelDependentBernoulliTrialCellCycleModel_3) +CellsGeneratorNoCellCycleModel_2 = DeprecatedClass("CellsGeneratorNoCellCycleModel_2", CellsGenerator_NoCellCycleModel_2) +CellsGeneratorNoCellCycleModel_3 = DeprecatedClass("CellsGeneratorNoCellCycleModel_3", CellsGenerator_NoCellCycleModel_3) +CellsGeneratorSimpleOxygenBasedCellCycleModel_2 = DeprecatedClass("CellsGeneratorSimpleOxygenBasedCellCycleModel_2", CellsGenerator_SimpleOxygenBasedCellCycleModel_2) +CellsGeneratorSimpleOxygenBasedCellCycleModel_3 = DeprecatedClass("CellsGeneratorSimpleOxygenBasedCellCycleModel_3", CellsGenerator_SimpleOxygenBasedCellCycleModel_3) +CellsGeneratorStochasticOxygenBasedCellCycleModel_2 = DeprecatedClass("CellsGeneratorStochasticOxygenBasedCellCycleModel_2", CellsGenerator_StochasticOxygenBasedCellCycleModel_2) +CellsGeneratorStochasticOxygenBasedCellCycleModel_3 = DeprecatedClass("CellsGeneratorStochasticOxygenBasedCellCycleModel_3", CellsGenerator_StochasticOxygenBasedCellCycleModel_3) +CellsGeneratorTysonNovakCellCycleModel_2 = DeprecatedClass("CellsGeneratorTysonNovakCellCycleModel_2", CellsGenerator_TysonNovakCellCycleModel_2) +CellsGeneratorTysonNovakCellCycleModel_3 = DeprecatedClass("CellsGeneratorTysonNovakCellCycleModel_3", CellsGenerator_TysonNovakCellCycleModel_3) +CellsGeneratorUniformCellCycleModel_2 = DeprecatedClass("CellsGeneratorUniformCellCycleModel_2", CellsGenerator_UniformCellCycleModel_2) +CellsGeneratorUniformCellCycleModel_3 = DeprecatedClass("CellsGeneratorUniformCellCycleModel_3", CellsGenerator_UniformCellCycleModel_3) +CellsGeneratorUniformG1GenerationalCellCycleModel_2 = DeprecatedClass("CellsGeneratorUniformG1GenerationalCellCycleModel_2", CellsGenerator_UniformG1GenerationalCellCycleModel_2) +CellsGeneratorUniformG1GenerationalCellCycleModel_3 = DeprecatedClass("CellsGeneratorUniformG1GenerationalCellCycleModel_3", CellsGenerator_UniformG1GenerationalCellCycleModel_3) +CellVolumesWriter2_2 = DeprecatedClass("CellVolumesWriter2_2", CellVolumesWriter_2_2) +CellVolumesWriter3_3 = DeprecatedClass("CellVolumesWriter3_3", CellVolumesWriter_3_3) +ChemotacticForce2 = DeprecatedClass("ChemotacticForce2", ChemotacticForce_2) +ChemotacticForce3 = DeprecatedClass("ChemotacticForce3", ChemotacticForce_3) +ChemotaxisPottsUpdateRule2 = DeprecatedClass("ChemotaxisPottsUpdateRule2", ChemotaxisPottsUpdateRule_2) +ChemotaxisPottsUpdateRule3 = DeprecatedClass("ChemotaxisPottsUpdateRule3", ChemotaxisPottsUpdateRule_3) +DeltaNotchEdgeInteriorTrackingModifier2 = DeprecatedClass("DeltaNotchEdgeInteriorTrackingModifier2", DeltaNotchEdgeInteriorTrackingModifier_2) +DeltaNotchEdgeInteriorTrackingModifier3 = DeprecatedClass("DeltaNotchEdgeInteriorTrackingModifier3", DeltaNotchEdgeInteriorTrackingModifier_3) +DeltaNotchEdgeTrackingModifier2 = DeprecatedClass("DeltaNotchEdgeTrackingModifier2", DeltaNotchEdgeTrackingModifier_2) +DeltaNotchEdgeTrackingModifier3 = DeprecatedClass("DeltaNotchEdgeTrackingModifier3", DeltaNotchEdgeTrackingModifier_3) +DeltaNotchTrackingModifier2 = DeprecatedClass("DeltaNotchTrackingModifier2", DeltaNotchTrackingModifier_2) +DeltaNotchTrackingModifier3 = DeprecatedClass("DeltaNotchTrackingModifier3", DeltaNotchTrackingModifier_3) +DifferentialAdhesionGeneralisedLinearSpringForce2_2 = DeprecatedClass("DifferentialAdhesionGeneralisedLinearSpringForce2_2", DifferentialAdhesionGeneralisedLinearSpringForce_2_2) +DifferentialAdhesionGeneralisedLinearSpringForce3_3 = DeprecatedClass("DifferentialAdhesionGeneralisedLinearSpringForce3_3", DifferentialAdhesionGeneralisedLinearSpringForce_3_3) +DifferentialAdhesionPottsUpdateRule2 = DeprecatedClass("DifferentialAdhesionPottsUpdateRule2", DifferentialAdhesionPottsUpdateRule_2) +DifferentialAdhesionPottsUpdateRule3 = DeprecatedClass("DifferentialAdhesionPottsUpdateRule3", DifferentialAdhesionPottsUpdateRule_3) +DiffusionCaUpdateRule2 = DeprecatedClass("DiffusionCaUpdateRule2", DiffusionCaUpdateRule_2) +DiffusionCaUpdateRule3 = DeprecatedClass("DiffusionCaUpdateRule3", DiffusionCaUpdateRule_3) +DiffusionForce2 = DeprecatedClass("DiffusionForce2", DiffusionForce_2) +DiffusionForce3 = DeprecatedClass("DiffusionForce3", DiffusionForce_3) +DivisionBiasTrackingModifier2 = DeprecatedClass("DivisionBiasTrackingModifier2", DivisionBiasTrackingModifier_2) +DivisionBiasTrackingModifier3 = DeprecatedClass("DivisionBiasTrackingModifier3", DivisionBiasTrackingModifier_3) +ExclusionCaBasedDivisionRule2 = DeprecatedClass("ExclusionCaBasedDivisionRule2", ExclusionCaBasedDivisionRule_2) +ExclusionCaBasedDivisionRule3 = DeprecatedClass("ExclusionCaBasedDivisionRule3", ExclusionCaBasedDivisionRule_3) +ExtrinsicPullModifier2 = DeprecatedClass("ExtrinsicPullModifier2", ExtrinsicPullModifier_2) +ExtrinsicPullModifier3 = DeprecatedClass("ExtrinsicPullModifier3", ExtrinsicPullModifier_3) +FarhadifarForce2 = DeprecatedClass("FarhadifarForce2", FarhadifarForce_2) +FarhadifarForce3 = DeprecatedClass("FarhadifarForce3", FarhadifarForce_3) +FixedCentreBasedDivisionRule2_2 = DeprecatedClass("FixedCentreBasedDivisionRule2_2", FixedCentreBasedDivisionRule_2_2) +FixedCentreBasedDivisionRule3_3 = DeprecatedClass("FixedCentreBasedDivisionRule3_3", FixedCentreBasedDivisionRule_3_3) +FixedVertexBasedDivisionRule2 = DeprecatedClass("FixedVertexBasedDivisionRule2", FixedVertexBasedDivisionRule_2) +FixedVertexBasedDivisionRule3 = DeprecatedClass("FixedVertexBasedDivisionRule3", FixedVertexBasedDivisionRule_3) +ForwardEulerNumericalMethod2_2 = DeprecatedClass("ForwardEulerNumericalMethod2_2", ForwardEulerNumericalMethod_2_2) +ForwardEulerNumericalMethod3_3 = DeprecatedClass("ForwardEulerNumericalMethod3_3", ForwardEulerNumericalMethod_3_3) +GeneralisedLinearSpringForce2_2 = DeprecatedClass("GeneralisedLinearSpringForce2_2", GeneralisedLinearSpringForce_2_2) +GeneralisedLinearSpringForce3_3 = DeprecatedClass("GeneralisedLinearSpringForce3_3", GeneralisedLinearSpringForce_3_3) +HeterotypicBoundaryLengthWriter2_2 = DeprecatedClass("HeterotypicBoundaryLengthWriter2_2", HeterotypicBoundaryLengthWriter_2_2) +HeterotypicBoundaryLengthWriter3_3 = DeprecatedClass("HeterotypicBoundaryLengthWriter3_3", HeterotypicBoundaryLengthWriter_3_3) +ImmersedBoundaryBoundaryCellWriter2_2 = DeprecatedClass("ImmersedBoundaryBoundaryCellWriter2_2", ImmersedBoundaryBoundaryCellWriter_2_2) +ImmersedBoundaryBoundaryCellWriter3_3 = DeprecatedClass("ImmersedBoundaryBoundaryCellWriter3_3", ImmersedBoundaryBoundaryCellWriter_3_3) +ImmersedBoundaryCellPopulation2 = DeprecatedClass("ImmersedBoundaryCellPopulation2", ImmersedBoundaryCellPopulation_2) +ImmersedBoundaryCellPopulation3 = DeprecatedClass("ImmersedBoundaryCellPopulation3", ImmersedBoundaryCellPopulation_3) +ImmersedBoundaryKinematicFeedbackForce2 = DeprecatedClass("ImmersedBoundaryKinematicFeedbackForce2", ImmersedBoundaryKinematicFeedbackForce_2) +ImmersedBoundaryKinematicFeedbackForce3 = DeprecatedClass("ImmersedBoundaryKinematicFeedbackForce3", ImmersedBoundaryKinematicFeedbackForce_3) +ImmersedBoundaryLinearDifferentialAdhesionForce2 = DeprecatedClass("ImmersedBoundaryLinearDifferentialAdhesionForce2", ImmersedBoundaryLinearDifferentialAdhesionForce_2) +ImmersedBoundaryLinearDifferentialAdhesionForce3 = DeprecatedClass("ImmersedBoundaryLinearDifferentialAdhesionForce3", ImmersedBoundaryLinearDifferentialAdhesionForce_3) +ImmersedBoundaryLinearInteractionForce2 = DeprecatedClass("ImmersedBoundaryLinearInteractionForce2", ImmersedBoundaryLinearInteractionForce_2) +ImmersedBoundaryLinearInteractionForce3 = DeprecatedClass("ImmersedBoundaryLinearInteractionForce3", ImmersedBoundaryLinearInteractionForce_3) +ImmersedBoundaryLinearMembraneForce2 = DeprecatedClass("ImmersedBoundaryLinearMembraneForce2", ImmersedBoundaryLinearMembraneForce_2) +ImmersedBoundaryLinearMembraneForce3 = DeprecatedClass("ImmersedBoundaryLinearMembraneForce3", ImmersedBoundaryLinearMembraneForce_3) +ImmersedBoundaryMorseInteractionForce2 = DeprecatedClass("ImmersedBoundaryMorseInteractionForce2", ImmersedBoundaryMorseInteractionForce_2) +ImmersedBoundaryMorseInteractionForce3 = DeprecatedClass("ImmersedBoundaryMorseInteractionForce3", ImmersedBoundaryMorseInteractionForce_3) +ImmersedBoundaryMorseMembraneForce2 = DeprecatedClass("ImmersedBoundaryMorseMembraneForce2", ImmersedBoundaryMorseMembraneForce_2) +ImmersedBoundaryMorseMembraneForce3 = DeprecatedClass("ImmersedBoundaryMorseMembraneForce3", ImmersedBoundaryMorseMembraneForce_3) +ImmersedBoundaryNeighbourNumberWriter2_2 = DeprecatedClass("ImmersedBoundaryNeighbourNumberWriter2_2", ImmersedBoundaryNeighbourNumberWriter_2_2) +ImmersedBoundaryNeighbourNumberWriter3_3 = DeprecatedClass("ImmersedBoundaryNeighbourNumberWriter3_3", ImmersedBoundaryNeighbourNumberWriter_3_3) +ImmersedBoundarySimulationModifier2 = DeprecatedClass("ImmersedBoundarySimulationModifier2", ImmersedBoundarySimulationModifier_2) +ImmersedBoundarySimulationModifier3 = DeprecatedClass("ImmersedBoundarySimulationModifier3", ImmersedBoundarySimulationModifier_3) +ImmersedBoundarySvgWriter2 = DeprecatedClass("ImmersedBoundarySvgWriter2", ImmersedBoundarySvgWriter_2) +ImmersedBoundarySvgWriter3 = DeprecatedClass("ImmersedBoundarySvgWriter3", ImmersedBoundarySvgWriter_3) +IsolatedLabelledCellKiller2 = DeprecatedClass("IsolatedLabelledCellKiller2", IsolatedLabelledCellKiller_2) +IsolatedLabelledCellKiller3 = DeprecatedClass("IsolatedLabelledCellKiller3", IsolatedLabelledCellKiller_3) +LegacyCellProliferativeTypesWriter2_2 = DeprecatedClass("LegacyCellProliferativeTypesWriter2_2", LegacyCellProliferativeTypesWriter_2_2) +LegacyCellProliferativeTypesWriter3_3 = DeprecatedClass("LegacyCellProliferativeTypesWriter3_3", LegacyCellProliferativeTypesWriter_3_3) +MeshBasedCellPopulation2_2 = DeprecatedClass("MeshBasedCellPopulation2_2", MeshBasedCellPopulation_2_2) +MeshBasedCellPopulation3_3 = DeprecatedClass("MeshBasedCellPopulation3_3", MeshBasedCellPopulation_3_3) +MeshBasedCellPopulationWithGhostNodes2 = DeprecatedClass("MeshBasedCellPopulationWithGhostNodes2", MeshBasedCellPopulationWithGhostNodes_2) +MeshBasedCellPopulationWithGhostNodes3 = DeprecatedClass("MeshBasedCellPopulationWithGhostNodes3", MeshBasedCellPopulationWithGhostNodes_3) +NagaiHondaDifferentialAdhesionForce2 = DeprecatedClass("NagaiHondaDifferentialAdhesionForce2", NagaiHondaDifferentialAdhesionForce_2) +NagaiHondaDifferentialAdhesionForce3 = DeprecatedClass("NagaiHondaDifferentialAdhesionForce3", NagaiHondaDifferentialAdhesionForce_3) +NagaiHondaForce2 = DeprecatedClass("NagaiHondaForce2", NagaiHondaForce_2) +NagaiHondaForce3 = DeprecatedClass("NagaiHondaForce3", NagaiHondaForce_3) +NodeBasedCellPopulation2 = DeprecatedClass("NodeBasedCellPopulation2", NodeBasedCellPopulation_2) +NodeBasedCellPopulation3 = DeprecatedClass("NodeBasedCellPopulation3", NodeBasedCellPopulation_3) +NodeBasedCellPopulationWithBuskeUpdate2 = DeprecatedClass("NodeBasedCellPopulationWithBuskeUpdate2", NodeBasedCellPopulationWithBuskeUpdate_2) +NodeBasedCellPopulationWithBuskeUpdate3 = DeprecatedClass("NodeBasedCellPopulationWithBuskeUpdate3", NodeBasedCellPopulationWithBuskeUpdate_3) +NodeBasedCellPopulationWithParticles2 = DeprecatedClass("NodeBasedCellPopulationWithParticles2", NodeBasedCellPopulationWithParticles_2) +NodeBasedCellPopulationWithParticles3 = DeprecatedClass("NodeBasedCellPopulationWithParticles3", NodeBasedCellPopulationWithParticles_3) +NodeLocationWriter2_2 = DeprecatedClass("NodeLocationWriter2_2", NodeLocationWriter_2_2) +NodeLocationWriter3_3 = DeprecatedClass("NodeLocationWriter3_3", NodeLocationWriter_3_3) +NodeVelocityWriter2_2 = DeprecatedClass("NodeVelocityWriter2_2", NodeVelocityWriter_2_2) +NodeVelocityWriter3_3 = DeprecatedClass("NodeVelocityWriter3_3", NodeVelocityWriter_3_3) +NormallyDistributedTargetAreaModifier2 = DeprecatedClass("NormallyDistributedTargetAreaModifier2", NormallyDistributedTargetAreaModifier_2) +NormallyDistributedTargetAreaModifier3 = DeprecatedClass("NormallyDistributedTargetAreaModifier3", NormallyDistributedTargetAreaModifier_3) +OffLatticeSimulation2_2 = DeprecatedClass("OffLatticeSimulation2_2", OffLatticeSimulation_2_2) +OffLatticeSimulation3_3 = DeprecatedClass("OffLatticeSimulation3_3", OffLatticeSimulation_3_3) +OnLatticeSimulation2 = DeprecatedClass("OnLatticeSimulation2", OnLatticeSimulation_2) +OnLatticeSimulation3 = DeprecatedClass("OnLatticeSimulation3", OnLatticeSimulation_3) +PlanarPolarisedFarhadifarForce2 = DeprecatedClass("PlanarPolarisedFarhadifarForce2", PlanarPolarisedFarhadifarForce_2) +PlanarPolarisedFarhadifarForce3 = DeprecatedClass("PlanarPolarisedFarhadifarForce3", PlanarPolarisedFarhadifarForce_3) +PlaneBasedCellKiller2 = DeprecatedClass("PlaneBasedCellKiller2", PlaneBasedCellKiller_2) +PlaneBasedCellKiller3 = DeprecatedClass("PlaneBasedCellKiller3", PlaneBasedCellKiller_3) +PlaneBoundaryCondition2_2 = DeprecatedClass("PlaneBoundaryCondition2_2", PlaneBoundaryCondition_2_2) +PlaneBoundaryCondition3_3 = DeprecatedClass("PlaneBoundaryCondition3_3", PlaneBoundaryCondition_3_3) +PottsBasedCellPopulation2 = DeprecatedClass("PottsBasedCellPopulation2", PottsBasedCellPopulation_2) +PottsBasedCellPopulation3 = DeprecatedClass("PottsBasedCellPopulation3", PottsBasedCellPopulation_3) +PythonSimulationModifier2 = DeprecatedClass("PythonSimulationModifier2", PythonSimulationModifier_2) +PythonSimulationModifier3 = DeprecatedClass("PythonSimulationModifier3", PythonSimulationModifier_3) +RadialCellDataDistributionWriter2_2 = DeprecatedClass("RadialCellDataDistributionWriter2_2", RadialCellDataDistributionWriter_2_2) +RadialCellDataDistributionWriter3_3 = DeprecatedClass("RadialCellDataDistributionWriter3_3", RadialCellDataDistributionWriter_3_3) +RandomCaSwitchingUpdateRule2 = DeprecatedClass("RandomCaSwitchingUpdateRule2", RandomCaSwitchingUpdateRule_2) +RandomCaSwitchingUpdateRule3 = DeprecatedClass("RandomCaSwitchingUpdateRule3", RandomCaSwitchingUpdateRule_3) +RandomCellKiller2 = DeprecatedClass("RandomCellKiller2", RandomCellKiller_2) +RandomCellKiller3 = DeprecatedClass("RandomCellKiller3", RandomCellKiller_3) +RandomDirectionCentreBasedDivisionRule2_2 = DeprecatedClass("RandomDirectionCentreBasedDivisionRule2_2", RandomDirectionCentreBasedDivisionRule_2_2) +RandomDirectionCentreBasedDivisionRule3_3 = DeprecatedClass("RandomDirectionCentreBasedDivisionRule3_3", RandomDirectionCentreBasedDivisionRule_3_3) +RandomDirectionVertexBasedDivisionRule2 = DeprecatedClass("RandomDirectionVertexBasedDivisionRule2", RandomDirectionVertexBasedDivisionRule_2) +RandomDirectionVertexBasedDivisionRule3 = DeprecatedClass("RandomDirectionVertexBasedDivisionRule3", RandomDirectionVertexBasedDivisionRule_3) +RepulsionForce2 = DeprecatedClass("RepulsionForce2", RepulsionForce_2) +RepulsionForce3 = DeprecatedClass("RepulsionForce3", RepulsionForce_3) +ShortAxisImmersedBoundaryDivisionRule2 = DeprecatedClass("ShortAxisImmersedBoundaryDivisionRule2", ShortAxisImmersedBoundaryDivisionRule_2) +ShortAxisImmersedBoundaryDivisionRule3 = DeprecatedClass("ShortAxisImmersedBoundaryDivisionRule3", ShortAxisImmersedBoundaryDivisionRule_3) +ShortAxisVertexBasedDivisionRule2 = DeprecatedClass("ShortAxisVertexBasedDivisionRule2", ShortAxisVertexBasedDivisionRule_2) +ShortAxisVertexBasedDivisionRule3 = DeprecatedClass("ShortAxisVertexBasedDivisionRule3", ShortAxisVertexBasedDivisionRule_3) +ShovingCaBasedDivisionRule2 = DeprecatedClass("ShovingCaBasedDivisionRule2", ShovingCaBasedDivisionRule_2) +ShovingCaBasedDivisionRule3 = DeprecatedClass("ShovingCaBasedDivisionRule3", ShovingCaBasedDivisionRule_3) +SimpleTargetAreaModifier2 = DeprecatedClass("SimpleTargetAreaModifier2", SimpleTargetAreaModifier_2) +SimpleTargetAreaModifier3 = DeprecatedClass("SimpleTargetAreaModifier3", SimpleTargetAreaModifier_3) +SlidingBoundaryCondition2 = DeprecatedClass("SlidingBoundaryCondition2", SlidingBoundaryCondition_2) +SlidingBoundaryCondition3 = DeprecatedClass("SlidingBoundaryCondition3", SlidingBoundaryCondition_3) +SphereGeometryBoundaryCondition2 = DeprecatedClass("SphereGeometryBoundaryCondition2", SphereGeometryBoundaryCondition_2) +SphereGeometryBoundaryCondition3 = DeprecatedClass("SphereGeometryBoundaryCondition3", SphereGeometryBoundaryCondition_3) +SurfaceAreaConstraintPottsUpdateRule2 = DeprecatedClass("SurfaceAreaConstraintPottsUpdateRule2", SurfaceAreaConstraintPottsUpdateRule_2) +SurfaceAreaConstraintPottsUpdateRule3 = DeprecatedClass("SurfaceAreaConstraintPottsUpdateRule3", SurfaceAreaConstraintPottsUpdateRule_3) +T2SwapCellKiller2 = DeprecatedClass("T2SwapCellKiller2", T2SwapCellKiller_2) +T2SwapCellKiller3 = DeprecatedClass("T2SwapCellKiller3", T2SwapCellKiller_3) +TargetAreaLinearGrowthModifier2 = DeprecatedClass("TargetAreaLinearGrowthModifier2", TargetAreaLinearGrowthModifier_2) +TargetAreaLinearGrowthModifier3 = DeprecatedClass("TargetAreaLinearGrowthModifier3", TargetAreaLinearGrowthModifier_3) +TargetedCellKiller2 = DeprecatedClass("TargetedCellKiller2", TargetedCellKiller_2) +TargetedCellKiller3 = DeprecatedClass("TargetedCellKiller3", TargetedCellKiller_3) +VertexBasedCellPopulation2 = DeprecatedClass("VertexBasedCellPopulation2", VertexBasedCellPopulation_2) +VertexBasedCellPopulation3 = DeprecatedClass("VertexBasedCellPopulation3", VertexBasedCellPopulation_3) +VertexBasedPopulationSrn2 = DeprecatedClass("VertexBasedPopulationSrn2", VertexBasedPopulationSrn_2) +VertexBasedPopulationSrn3 = DeprecatedClass("VertexBasedPopulationSrn3", VertexBasedPopulationSrn_3) +VertexIntersectionSwapLocationsWriter2_2 = DeprecatedClass("VertexIntersectionSwapLocationsWriter2_2", VertexIntersectionSwapLocationsWriter_2_2) +VertexIntersectionSwapLocationsWriter3_3 = DeprecatedClass("VertexIntersectionSwapLocationsWriter3_3", VertexIntersectionSwapLocationsWriter_3_3) +VertexT1SwapLocationsWriter2_2 = DeprecatedClass("VertexT1SwapLocationsWriter2_2", VertexT1SwapLocationsWriter_2_2) +VertexT1SwapLocationsWriter3_3 = DeprecatedClass("VertexT1SwapLocationsWriter3_3", VertexT1SwapLocationsWriter_3_3) +VertexT2SwapLocationsWriter2_2 = DeprecatedClass("VertexT2SwapLocationsWriter2_2", VertexT2SwapLocationsWriter_2_2) +VertexT2SwapLocationsWriter3_3 = DeprecatedClass("VertexT2SwapLocationsWriter3_3", VertexT2SwapLocationsWriter_3_3) +VertexT3SwapLocationsWriter2_2 = DeprecatedClass("VertexT3SwapLocationsWriter2_2", VertexT3SwapLocationsWriter_2_2) +VertexT3SwapLocationsWriter3_3 = DeprecatedClass("VertexT3SwapLocationsWriter3_3", VertexT3SwapLocationsWriter_3_3) +VolumeConstraintPottsUpdateRule2 = DeprecatedClass("VolumeConstraintPottsUpdateRule2", VolumeConstraintPottsUpdateRule_2) +VolumeConstraintPottsUpdateRule3 = DeprecatedClass("VolumeConstraintPottsUpdateRule3", VolumeConstraintPottsUpdateRule_3) +VolumeTrackingModifier2 = DeprecatedClass("VolumeTrackingModifier2", VolumeTrackingModifier_2) +VolumeTrackingModifier3 = DeprecatedClass("VolumeTrackingModifier3", VolumeTrackingModifier_3) +VonMisesVertexBasedDivisionRule2 = DeprecatedClass("VonMisesVertexBasedDivisionRule2", VonMisesVertexBasedDivisionRule_2) +VonMisesVertexBasedDivisionRule3 = DeprecatedClass("VonMisesVertexBasedDivisionRule3", VonMisesVertexBasedDivisionRule_3) +VoronoiDataWriter2_2 = DeprecatedClass("VoronoiDataWriter2_2", VoronoiDataWriter_2_2) +VoronoiDataWriter3_3 = DeprecatedClass("VoronoiDataWriter3_3", VoronoiDataWriter_3_3) +VtkSceneModifier2 = DeprecatedClass("VtkSceneModifier2", VtkSceneModifier_2) +VtkSceneModifier3 = DeprecatedClass("VtkSceneModifier3", VtkSceneModifier_3) +WelikyOsterForce2 = DeprecatedClass("WelikyOsterForce2", WelikyOsterForce_2) +WelikyOsterForce3 = DeprecatedClass("WelikyOsterForce3", WelikyOsterForce_3) diff --git a/pychaste/src/py/chaste/cell_based/_fortests.py b/pychaste/src/py/chaste/cell_based/_fortests.py new file mode 100644 index 0000000000..fe24a7347c --- /dev/null +++ b/pychaste/src/py/chaste/cell_based/_fortests.py @@ -0,0 +1,76 @@ +"""Helper classes for running cell-based tests.""" + +__copyright__ = """Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import unittest + +import chaste.cell_based +import chaste.core + + +def SetupNotebookTest(): + simulation_time = chaste.cell_based.SimulationTime.Instance() + simulation_time.SetStartTime(0.0) + chaste.core.RandomNumberGenerator.Instance().Reseed(0) + chaste.cell_based.CellId.ResetMaxCellId() + + +def TearDownNotebookTest(): + simulation_time = chaste.cell_based.SimulationTime.Instance() + simulation_time.Destroy() + chaste.core.RandomNumberGenerator.Instance().Destroy() + + +class AbstractCellBasedTestSuite(unittest.TestCase): + + def setUp(self): + simulation_time = chaste.cell_based.SimulationTime.Instance() + simulation_time.SetStartTime(0.0) + chaste.core.RandomNumberGenerator.Instance().Reseed(0) + chaste.cell_based.CellId.ResetMaxCellId() + + def tearDown(self): + simulation_time = chaste.cell_based.SimulationTime.Instance() + simulation_time.Destroy() + chaste.core.RandomNumberGenerator.Instance().Destroy() + + +class AbstractCellBasedWithTimingsTestSuite(AbstractCellBasedTestSuite): + + def setUp(self): + chaste.core.Timer().Reset() + super(AbstractCellBasedWithTimingsTestSuite, self).setUp() + + def tearDown(self): + super(AbstractCellBasedWithTimingsTestSuite, self).tearDown() + chaste.core.Timer().Print("Test elapsed") diff --git a/pychaste/src/py/chaste/core/__init__.py b/pychaste/src/py/chaste/core/__init__.py new file mode 100644 index 0000000000..a92a11ba4e --- /dev/null +++ b/pychaste/src/py/chaste/core/__init__.py @@ -0,0 +1,48 @@ +"""Core Module""" + +__copyright__ = """Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +from chaste._pychaste_all import ( + ChasteBuildInfo, + FileFinder, + Identifiable, + OutputFileHandler, + PetscSetupUtils, + PetscTools, + ProgressReporter, + RandomNumberGenerator, + RelativeTo, + ReplicatableVector, + Timer, + TimeStepper, +) diff --git a/pychaste/src/py/chaste/external/Detector.js b/pychaste/src/py/chaste/external/Detector.js new file mode 100644 index 0000000000..a476103b00 --- /dev/null +++ b/pychaste/src/py/chaste/external/Detector.js @@ -0,0 +1,66 @@ +/** + * @author alteredq / http://alteredqualia.com/ + * @author mr.doob / http://mrdoob.com/ + */ + +var Detector = { + + canvas: !! window.CanvasRenderingContext2D, + webgl: ( function () { try { var canvas = document.createElement( 'canvas' ); return !! ( window.WebGLRenderingContext && ( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) ) ); } catch( e ) { return false; } } )(), + workers: !! window.Worker, + fileapi: window.File && window.FileReader && window.FileList && window.Blob, + + getWebGLErrorMessage: function () { + + var element = document.createElement( 'div' ); + element.id = 'webgl-error-message'; + element.style.fontFamily = 'monospace'; + element.style.fontSize = '13px'; + element.style.fontWeight = 'normal'; + element.style.textAlign = 'center'; + element.style.background = '#fff'; + element.style.color = '#000'; + element.style.padding = '1.5em'; + element.style.width = '400px'; + element.style.margin = '5em auto 0'; + + if ( ! this.webgl ) { + + element.innerHTML = window.WebGLRenderingContext ? [ + 'Your graphics card does not seem to support WebGL.
', + 'Find out how to get it here.' + ].join( '\n' ) : [ + 'Your browser does not seem to support WebGL.
', + 'Find out how to get it here.' + ].join( '\n' ); + + } + + return element; + + }, + + addGetWebGLMessage: function ( parameters ) { + + var parent, id, element; + + parameters = parameters || {}; + + parent = parameters.parent !== undefined ? parameters.parent : document.body; + id = parameters.id !== undefined ? parameters.id : 'oldie'; + + element = Detector.getWebGLErrorMessage(); + element.id = id; + + parent.appendChild( element ); + + } + +}; + +// browserify support +if ( typeof module === 'object' ) { + + module.exports = Detector; + +} diff --git a/pychaste/src/py/chaste/external/LICENSE b/pychaste/src/py/chaste/external/LICENSE new file mode 100644 index 0000000000..d33709fbf0 --- /dev/null +++ b/pychaste/src/py/chaste/external/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright © 2010-2024 three.js authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/pychaste/src/py/chaste/external/OrbitControls.js b/pychaste/src/py/chaste/external/OrbitControls.js new file mode 100644 index 0000000000..9faea1f030 --- /dev/null +++ b/pychaste/src/py/chaste/external/OrbitControls.js @@ -0,0 +1,672 @@ +/** + * @author qiao / https://github.com/qiao + * @author mrdoob / http://mrdoob.com + * @author alteredq / http://alteredqualia.com/ + * @author WestLangley / http://github.com/WestLangley + * @author erich666 / http://erichaines.com + */ +/*global THREE, console */ + +// This set of controls performs orbiting, dollying (zooming), and panning. It maintains +// the "up" direction as +Y, unlike the TrackballControls. Touch on tablet and phones is +// supported. +// +// Orbit - left mouse / touch: one finger move +// Zoom - middle mouse, or mousewheel / touch: two finger spread or squish +// Pan - right mouse, or arrow keys / touch: three finter swipe +// +// This is a drop-in replacement for (most) TrackballControls used in examples. +// That is, include this js file and wherever you see: +// controls = new THREE.TrackballControls( camera ); +// controls.target.z = 150; +// Simple substitute "OrbitControls" and the control should work as-is. + +THREE.OrbitControls = function ( object, domElement ) { + + this.object = object; + this.domElement = ( domElement !== undefined ) ? domElement : document; + + // API + + // Set to false to disable this control + this.enabled = true; + + // "target" sets the location of focus, where the control orbits around + // and where it pans with respect to. + this.target = new THREE.Vector3(); + + // center is old, deprecated; use "target" instead + this.center = this.target; + + // This option actually enables dollying in and out; left as "zoom" for + // backwards compatibility + this.noZoom = false; + this.zoomSpeed = 1.0; + + // Limits to how far you can dolly in and out + this.minDistance = 0; + this.maxDistance = Infinity; + + // Set to true to disable this control + this.noRotate = false; + this.rotateSpeed = 1.0; + + // Set to true to disable this control + this.noPan = false; + this.keyPanSpeed = 7.0; // pixels moved per arrow key push + + // Set to true to automatically rotate around the target + this.autoRotate = false; + this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 + + // How far you can orbit vertically, upper and lower limits. + // Range is 0 to Math.PI radians. + this.minPolarAngle = 0; // radians + this.maxPolarAngle = Math.PI; // radians + + // How far you can orbit horizontally, upper and lower limits. + // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. + this.minAzimuthAngle = - Infinity; // radians + this.maxAzimuthAngle = Infinity; // radians + + // Set to true to disable use of the keys + this.noKeys = false; + + // The four arrow keys + this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; + + // Mouse buttons + this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT }; + + //////////// + // internals + + var scope = this; + + var EPS = 0.000001; + + var rotateStart = new THREE.Vector2(); + var rotateEnd = new THREE.Vector2(); + var rotateDelta = new THREE.Vector2(); + + var panStart = new THREE.Vector2(); + var panEnd = new THREE.Vector2(); + var panDelta = new THREE.Vector2(); + var panOffset = new THREE.Vector3(); + + var offset = new THREE.Vector3(); + + var dollyStart = new THREE.Vector2(); + var dollyEnd = new THREE.Vector2(); + var dollyDelta = new THREE.Vector2(); + + var theta; + var phi; + var phiDelta = 0; + var thetaDelta = 0; + var scale = 1; + var pan = new THREE.Vector3(); + + var lastPosition = new THREE.Vector3(); + var lastQuaternion = new THREE.Quaternion(); + + var STATE = { NONE : -1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 }; + + var state = STATE.NONE; + + // for reset + + this.target0 = this.target.clone(); + this.position0 = this.object.position.clone(); + + // so camera.up is the orbit axis + + var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); + var quatInverse = quat.clone().inverse(); + + // events + + var changeEvent = { type: 'change' }; + var startEvent = { type: 'start'}; + var endEvent = { type: 'end'}; + + this.rotateLeft = function ( angle ) { + + if ( angle === undefined ) { + + angle = getAutoRotationAngle(); + + } + + thetaDelta -= angle; + + }; + + this.rotateUp = function ( angle ) { + + if ( angle === undefined ) { + + angle = getAutoRotationAngle(); + + } + + phiDelta -= angle; + + }; + + // pass in distance in world space to move left + this.panLeft = function ( distance ) { + + var te = this.object.matrix.elements; + + // get X column of matrix + panOffset.set( te[ 0 ], te[ 1 ], te[ 2 ] ); + panOffset.multiplyScalar( - distance ); + + pan.add( panOffset ); + + }; + + // pass in distance in world space to move up + this.panUp = function ( distance ) { + + var te = this.object.matrix.elements; + + // get Y column of matrix + panOffset.set( te[ 4 ], te[ 5 ], te[ 6 ] ); + panOffset.multiplyScalar( distance ); + + pan.add( panOffset ); + + }; + + // pass in x,y of change desired in pixel space, + // right and down are positive + this.pan = function ( deltaX, deltaY ) { + + var element = scope.domElement === document ? scope.domElement.body : scope.domElement; + + if ( scope.object.fov !== undefined ) { + + // perspective + var position = scope.object.position; + var offset = position.clone().sub( scope.target ); + var targetDistance = offset.length(); + + // half of the fov is center to top of screen + targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); + + // we actually don't use screenWidth, since perspective camera is fixed to screen height + scope.panLeft( 2 * deltaX * targetDistance / element.clientHeight ); + scope.panUp( 2 * deltaY * targetDistance / element.clientHeight ); + + } else if ( scope.object.top !== undefined ) { + + // orthographic + scope.panLeft( deltaX * (scope.object.right - scope.object.left) / element.clientWidth ); + scope.panUp( deltaY * (scope.object.top - scope.object.bottom) / element.clientHeight ); + + } else { + + // camera neither orthographic or perspective + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); + + } + + }; + + this.dollyIn = function ( dollyScale ) { + + if ( dollyScale === undefined ) { + + dollyScale = getZoomScale(); + + } + + scale /= dollyScale; + + }; + + this.dollyOut = function ( dollyScale ) { + + if ( dollyScale === undefined ) { + + dollyScale = getZoomScale(); + + } + + scale *= dollyScale; + + }; + + this.update = function () { + + var position = this.object.position; + + offset.copy( position ).sub( this.target ); + + // rotate offset to "y-axis-is-up" space + offset.applyQuaternion( quat ); + + // angle from z-axis around y-axis + + theta = Math.atan2( offset.x, offset.z ); + + // angle from y-axis + + phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y ); + + if ( this.autoRotate ) { + + this.rotateLeft( getAutoRotationAngle() ); + + } + + theta += thetaDelta; + phi += phiDelta; + + // restrict theta to be between desired limits + theta = Math.max( this.minAzimuthAngle, Math.min( this.maxAzimuthAngle, theta ) ); + + // restrict phi to be between desired limits + phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) ); + + // restrict phi to be betwee EPS and PI-EPS + phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) ); + + var radius = offset.length() * scale; + + // restrict radius to be between desired limits + radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) ); + + // move target to panned location + this.target.add( pan ); + + offset.x = radius * Math.sin( phi ) * Math.sin( theta ); + offset.y = radius * Math.cos( phi ); + offset.z = radius * Math.sin( phi ) * Math.cos( theta ); + + // rotate offset back to "camera-up-vector-is-up" space + offset.applyQuaternion( quatInverse ); + + position.copy( this.target ).add( offset ); + + this.object.lookAt( this.target ); + + thetaDelta = 0; + phiDelta = 0; + scale = 1; + pan.set( 0, 0, 0 ); + + // update condition is: + // min(camera displacement, camera rotation in radians)^2 > EPS + // using small-angle approximation cos(x/2) = 1 - x^2 / 8 + + if ( lastPosition.distanceToSquared( this.object.position ) > EPS + || 8 * (1 - lastQuaternion.dot(this.object.quaternion)) > EPS ) { + + this.dispatchEvent( changeEvent ); + + lastPosition.copy( this.object.position ); + lastQuaternion.copy (this.object.quaternion ); + + } + + }; + + + this.reset = function () { + + state = STATE.NONE; + + this.target.copy( this.target0 ); + this.object.position.copy( this.position0 ); + + this.update(); + + }; + + this.getPolarAngle = function () { + + return phi; + + }; + + this.getAzimuthalAngle = function () { + + return theta + + }; + + function getAutoRotationAngle() { + + return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; + + } + + function getZoomScale() { + + return Math.pow( 0.95, scope.zoomSpeed ); + + } + + function onMouseDown( event ) { + + if ( scope.enabled === false ) return; + event.preventDefault(); + + if ( event.button === scope.mouseButtons.ORBIT ) { + if ( scope.noRotate === true ) return; + + state = STATE.ROTATE; + + rotateStart.set( event.clientX, event.clientY ); + + } else if ( event.button === scope.mouseButtons.ZOOM ) { + if ( scope.noZoom === true ) return; + + state = STATE.DOLLY; + + dollyStart.set( event.clientX, event.clientY ); + + } else if ( event.button === scope.mouseButtons.PAN ) { + if ( scope.noPan === true ) return; + + state = STATE.PAN; + + panStart.set( event.clientX, event.clientY ); + + } + + document.addEventListener( 'mousemove', onMouseMove, false ); + document.addEventListener( 'mouseup', onMouseUp, false ); + scope.dispatchEvent( startEvent ); + + } + + function onMouseMove( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + var element = scope.domElement === document ? scope.domElement.body : scope.domElement; + + if ( state === STATE.ROTATE ) { + + if ( scope.noRotate === true ) return; + + rotateEnd.set( event.clientX, event.clientY ); + rotateDelta.subVectors( rotateEnd, rotateStart ); + + // rotating across whole screen goes 360 degrees around + scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); + + // rotating up and down along whole screen attempts to go 360, but limited to 180 + scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); + + rotateStart.copy( rotateEnd ); + + } else if ( state === STATE.DOLLY ) { + + if ( scope.noZoom === true ) return; + + dollyEnd.set( event.clientX, event.clientY ); + dollyDelta.subVectors( dollyEnd, dollyStart ); + + if ( dollyDelta.y > 0 ) { + + scope.dollyIn(); + + } else { + + scope.dollyOut(); + + } + + dollyStart.copy( dollyEnd ); + + } else if ( state === STATE.PAN ) { + + if ( scope.noPan === true ) return; + + panEnd.set( event.clientX, event.clientY ); + panDelta.subVectors( panEnd, panStart ); + + scope.pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + } + + scope.update(); + + } + + function onMouseUp( /* event */ ) { + + if ( scope.enabled === false ) return; + + document.removeEventListener( 'mousemove', onMouseMove, false ); + document.removeEventListener( 'mouseup', onMouseUp, false ); + scope.dispatchEvent( endEvent ); + state = STATE.NONE; + + } + + function onMouseWheel( event ) { + + if ( scope.enabled === false || scope.noZoom === true ) return; + + event.preventDefault(); + event.stopPropagation(); + + var delta = 0; + + if ( event.wheelDelta !== undefined ) { // WebKit / Opera / Explorer 9 + + delta = event.wheelDelta; + + } else if ( event.detail !== undefined ) { // Firefox + + delta = - event.detail; + + } + + if ( delta > 0 ) { + + scope.dollyOut(); + + } else { + + scope.dollyIn(); + + } + + scope.update(); + scope.dispatchEvent( startEvent ); + scope.dispatchEvent( endEvent ); + + } + + function onKeyDown( event ) { + + if ( scope.enabled === false || scope.noKeys === true || scope.noPan === true ) return; + + switch ( event.keyCode ) { + + case scope.keys.UP: + scope.pan( 0, scope.keyPanSpeed ); + scope.update(); + break; + + case scope.keys.BOTTOM: + scope.pan( 0, - scope.keyPanSpeed ); + scope.update(); + break; + + case scope.keys.LEFT: + scope.pan( scope.keyPanSpeed, 0 ); + scope.update(); + break; + + case scope.keys.RIGHT: + scope.pan( - scope.keyPanSpeed, 0 ); + scope.update(); + break; + + } + + } + + function touchstart( event ) { + + if ( scope.enabled === false ) return; + + switch ( event.touches.length ) { + + case 1: // one-fingered touch: rotate + + if ( scope.noRotate === true ) return; + + state = STATE.TOUCH_ROTATE; + + rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + break; + + case 2: // two-fingered touch: dolly + + if ( scope.noZoom === true ) return; + + state = STATE.TOUCH_DOLLY; + + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + var distance = Math.sqrt( dx * dx + dy * dy ); + dollyStart.set( 0, distance ); + break; + + case 3: // three-fingered touch: pan + + if ( scope.noPan === true ) return; + + state = STATE.TOUCH_PAN; + + panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + break; + + default: + + state = STATE.NONE; + + } + + scope.dispatchEvent( startEvent ); + + } + + function touchmove( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + event.stopPropagation(); + + var element = scope.domElement === document ? scope.domElement.body : scope.domElement; + + switch ( event.touches.length ) { + + case 1: // one-fingered touch: rotate + + if ( scope.noRotate === true ) return; + if ( state !== STATE.TOUCH_ROTATE ) return; + + rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + rotateDelta.subVectors( rotateEnd, rotateStart ); + + // rotating across whole screen goes 360 degrees around + scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); + // rotating up and down along whole screen attempts to go 360, but limited to 180 + scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); + + rotateStart.copy( rotateEnd ); + + scope.update(); + break; + + case 2: // two-fingered touch: dolly + + if ( scope.noZoom === true ) return; + if ( state !== STATE.TOUCH_DOLLY ) return; + + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + var distance = Math.sqrt( dx * dx + dy * dy ); + + dollyEnd.set( 0, distance ); + dollyDelta.subVectors( dollyEnd, dollyStart ); + + if ( dollyDelta.y > 0 ) { + + scope.dollyOut(); + + } else { + + scope.dollyIn(); + + } + + dollyStart.copy( dollyEnd ); + + scope.update(); + break; + + case 3: // three-fingered touch: pan + + if ( scope.noPan === true ) return; + if ( state !== STATE.TOUCH_PAN ) return; + + panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + panDelta.subVectors( panEnd, panStart ); + + scope.pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + scope.update(); + break; + + default: + + state = STATE.NONE; + + } + + } + + function touchend( /* event */ ) { + + if ( scope.enabled === false ) return; + + scope.dispatchEvent( endEvent ); + state = STATE.NONE; + + } + + this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false ); + this.domElement.addEventListener( 'mousedown', onMouseDown, false ); + this.domElement.addEventListener( 'mousewheel', onMouseWheel, false ); + this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox + + this.domElement.addEventListener( 'touchstart', touchstart, false ); + this.domElement.addEventListener( 'touchend', touchend, false ); + this.domElement.addEventListener( 'touchmove', touchmove, false ); + + window.addEventListener( 'keydown', onKeyDown, false ); + + // force an update at start + this.update(); + +}; + +THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); diff --git a/pychaste/src/py/chaste/external/VRMLLoader.js b/pychaste/src/py/chaste/external/VRMLLoader.js new file mode 100644 index 0000000000..45adf00e1f --- /dev/null +++ b/pychaste/src/py/chaste/external/VRMLLoader.js @@ -0,0 +1,839 @@ +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.VRMLLoader = function () {}; + +THREE.VRMLLoader.prototype = { + + constructor: THREE.VRMLLoader, + + // for IndexedFaceSet support + isRecordingPoints: false, + isRecordingFaces: false, + points: [], + indexes : [], + + // for Background support + isRecordingAngles: false, + isRecordingColors: false, + angles: [], + colors: [], + + recordingFieldname: null, + + load: function ( url, callback ) { + + var scope = this; + var request = new XMLHttpRequest(); + + request.addEventListener( 'load', function ( event ) { + + var object = scope.parse( event.target.responseText ); + + scope.dispatchEvent( { type: 'load', content: object } ); + + if ( callback ) callback( object ); + + }, false ); + + request.addEventListener( 'progress', function ( event ) { + + scope.dispatchEvent( { type: 'progress', loaded: event.loaded, total: event.total } ); + + }, false ); + + request.addEventListener( 'error', function () { + + scope.dispatchEvent( { type: 'error', message: 'Couldn\'t load URL [' + url + ']' } ); + + }, false ); + + request.open( 'GET', url, true ); + request.send( null ); + + }, + + parse: function ( data ) { + + var parseV1 = function ( lines, scene ) { + + console.warn( 'VRML V1.0 not supported yet' ); + + }; + + var parseV2 = function ( lines, scene ) { + + var defines = {}; + var float_pattern = /(\b|\-|\+)([\d\.e]+)/; + var float3_pattern = /([\d\.\+\-e]+)\s+([\d\.\+\-e]+)\s+([\d\.\+\-e]+)/g; + + /** + * Interpolates colors a and b following their relative distance + * expressed by t. + * + * @param float a + * @param float b + * @param float t + * @returns {Color} + */ + var interpolateColors = function(a, b, t) { + var deltaR = a.r - b.r; + var deltaG = a.g - b.g; + var deltaB = a.b - b.b; + + var c = new THREE.Color(); + + c.r = a.r - t * deltaR; + c.g = a.g - t * deltaG; + c.b = a.b - t * deltaB; + + return c; + }; + + /** + * Vertically paints the faces interpolating between the + * specified colors at the specified angels. This is used for the Background + * node, but could be applied to other nodes with multiple faces as well. + * + * When used with the Background node, default is directionIsDown is true if + * interpolating the skyColor down from the Zenith. When interpolationg up from + * the Nadir i.e. interpolating the groundColor, the directionIsDown is false. + * + * The first angle is never specified, it is the Zenith (0 rad). Angles are specified + * in radians. The geometry is thought a sphere, but could be anything. The color interpolation + * is linear along the Y axis in any case. + * + * You must specify one more color than you have angles at the beginning of the colors array. + * This is the color of the Zenith (the top of the shape). + * + * @param geometry + * @param radius + * @param angles + * @param colors + * @param boolean directionIsDown Whether to work bottom up or top down. + */ + var paintFaces = function (geometry, radius, angles, colors, directionIsDown) { + + var f, n, p, vertexIndex, color; + + var direction = directionIsDown ? 1 : -1; + + var faceIndices = [ 'a', 'b', 'c', 'd' ]; + + var coord = [ ], aColor, bColor, t = 1, A = {}, B = {}, applyColor = false, colorIndex; + + for ( var k = 0; k < angles.length; k++ ) { + + var vec = { }; + + // push the vector at which the color changes + vec.y = direction * ( Math.cos( angles[k] ) * radius); + + vec.x = direction * ( Math.sin( angles[k] ) * radius); + + coord.push( vec ); + + } + + // painting the colors on the faces + for ( var i = 0; i < geometry.faces.length ; i++ ) { + + f = geometry.faces[ i ]; + + n = ( f instanceof THREE.Face3 ) ? 3 : 4; + + for ( var j = 0; j < n; j++ ) { + + vertexIndex = f[ faceIndices[ j ] ]; + + p = geometry.vertices[ vertexIndex ]; + + for ( var index = 0; index < colors.length; index++ ) { + + // linear interpolation between aColor and bColor, calculate proportion + // A is previous point (angle) + if ( index === 0 ) { + + A.x = 0; + A.y = directionIsDown ? radius : -1 * radius; + + } else { + + A.x = coord[ index-1 ].x; + A.y = coord[ index-1 ].y; + + } + + // B is current point (angle) + B = coord[index]; + + if ( undefined !== B ) { + // p has to be between the points A and B which we interpolate + applyColor = directionIsDown ? p.y <= A.y && p.y > B.y : p.y >= A.y && p.y < B.y; + + if (applyColor) { + + bColor = colors[ index + 1 ]; + + aColor = colors[ index ]; + + // below is simple linear interpolation + t = Math.abs( p.y - A.y ) / ( A.y - B.y ); + + // to make it faster, you can only calculate this if the y coord changes, the color is the same for points with the same y + color = interpolateColors( aColor, bColor, t ); + + f.vertexColors[ j ] = color; + } + + } else if ( undefined === f.vertexColors[ j ] ) { + colorIndex = directionIsDown ? colors.length -1 : 0; + f.vertexColors[ j ] = colors[ colorIndex ]; + + } + } + + } + + } + }; + + var parseProperty = function (node, line) { + + var parts = [], part, property = {}, fieldName; + + /** + * Expression for matching relevant information, such as a name or value, but not the separators + * @type {RegExp} + */ + var regex = /[^\s,\[\]]+/g; + + var point, index, angles, colors; + + while (null != ( part = regex.exec(line) ) ) { + parts.push(part[0]); + } + + fieldName = parts[0]; + + + // trigger several recorders + switch (fieldName) { + case 'skyAngle': + case 'groundAngle': + this.recordingFieldname = fieldName; + this.isRecordingAngles = true; + this.angles = []; + break; + case 'skyColor': + case 'groundColor': + this.recordingFieldname = fieldName; + this.isRecordingColors = true; + this.colors = []; + break; + case 'point': + this.recordingFieldname = fieldName; + this.isRecordingPoints = true; + this.points = []; + break; + case 'coordIndex': + this.recordingFieldname = fieldName; + this.isRecordingFaces = true; + this.indexes = []; + break; + } + + if (this.isRecordingFaces) { + + // the parts hold the indexes as strings + if (parts.length > 0) { + index = []; + + for (var ind = 0;ind < parts.length; ind++) { + + // the part should either be positive integer or -1 + if (!/(-?\d+)/.test( parts[ind]) ) { + continue; + } + + // end of current face + if (parts[ind] === "-1") { + if (index.length > 0) { + this.indexes.push(index); + } + + // start new one + index = []; + } else { + index.push(parseInt( parts[ind]) ); + } + } + + } + + // end + if (/]/.exec(line)) { + this.isRecordingFaces = false; + node.coordIndex = this.indexes; + } + + } else if (this.isRecordingPoints) { + + while ( null !== ( parts = float3_pattern.exec(line) ) ) { + point = { + x: parseFloat(parts[1]), + y: parseFloat(parts[2]), + z: parseFloat(parts[3]) + }; + + this.points.push(point); + } + + // end + if ( /]/.exec(line) ) { + this.isRecordingPoints = false; + node.points = this.points; + } + + } else if ( this.isRecordingAngles ) { + + // the parts hold the angles as strings + if ( parts.length > 0 ) { + + for ( var ind = 0;ind < parts.length; ind++ ) { + + // the part should be a float + if ( ! float_pattern.test( parts[ind] ) ) { + continue; + } + + this.angles.push( parseFloat( parts[ind] ) ); + } + + } + + // end + if ( /]/.exec(line) ) { + this.isRecordingAngles = false; + node[this.recordingFieldname] = this.angles; + } + + } else if (this.isRecordingColors) { + + while( null !== ( parts = float3_pattern.exec(line) ) ) { + + color = { + r: parseFloat(parts[1]), + g: parseFloat(parts[2]), + b: parseFloat(parts[3]) + }; + + this.colors.push(color); + + } + + // end + if (/]/.exec(line)) { + this.isRecordingColors = false; + node[this.recordingFieldname] = this.colors; + } + + } else if ( parts[parts.length -1] !== 'NULL' && fieldName !== 'children') { + + switch (fieldName) { + + case 'diffuseColor': + case 'emissiveColor': + case 'specularColor': + case 'color': + + if (parts.length != 4) { + console.warn('Invalid color format detected for ' + fieldName ); + break; + } + + property = { + r: parseFloat(parts[1]), + g: parseFloat(parts[2]), + b: parseFloat(parts[3]) + } + + break; + + case 'translation': + case 'scale': + case 'size': + if (parts.length != 4) { + console.warn('Invalid vector format detected for ' + fieldName); + break; + } + + property = { + x: parseFloat(parts[1]), + y: parseFloat(parts[2]), + z: parseFloat(parts[3]) + } + + break; + + case 'radius': + case 'topRadius': + case 'bottomRadius': + case 'height': + case 'transparency': + case 'shininess': + case 'ambientIntensity': + if (parts.length != 2) { + console.warn('Invalid single float value specification detected for ' + fieldName); + break; + } + + property = parseFloat(parts[1]); + + break; + + case 'rotation': + if (parts.length != 5) { + console.warn('Invalid quaternion format detected for ' + fieldName); + break; + } + + property = { + x: parseFloat(parts[1]), + y: parseFloat(parts[2]), + z: parseFloat(parts[3]), + w: parseFloat(parts[4]) + } + + break; + + case 'ccw': + case 'solid': + case 'colorPerVertex': + case 'convex': + if (parts.length != 2) { + console.warn('Invalid format detected for ' + fieldName); + break; + } + + property = parts[1] === 'TRUE' ? true : false; + + break; + } + + node[fieldName] = property; + } + + return property; + }; + + var getTree = function ( lines ) { + + var tree = { 'string': 'Scene', children: [] }; + var current = tree; + var matches; + var specification; + + for ( var i = 0; i < lines.length; i ++ ) { + + var comment = ''; + + var line = lines[ i ]; + + // omit whitespace only lines + if ( null !== ( result = /^\s+?$/g.exec( line ) ) ) { + continue; + } + + line = line.trim(); + + // skip empty lines + if (line === '') { + continue; + } + + if ( /#/.exec( line ) ) { + + var parts = line.split('#'); + + // discard everything after the #, it is a comment + line = parts[0]; + + // well, let's also keep the comment + comment = parts[1]; + } + + if ( matches = /([^\s]*){1}\s?{/.exec( line ) ) { // first subpattern should match the Node name + + var block = { 'nodeType' : matches[1], 'string': line, 'parent': current, 'children': [],'comment' : comment}; + current.children.push( block ); + current = block; + + if ( /}/.exec( line ) ) { + // example: geometry Box { size 1 1 1 } # all on the same line + specification = /{(.*)}/.exec( line )[ 1 ]; + + // todo: remove once new parsing is complete? + block.children.push( specification ); + + parseProperty(current, specification); + + current = current.parent; + + } + + } else if ( /}/.exec( line ) ) { + + current = current.parent; + + } else if ( line !== '' ) { + + parseProperty(current, line); + // todo: remove once new parsing is complete? we still do not parse geometry and appearance the new way + current.children.push( line ); + + } + + } + + return tree; + } + + var parseNode = function ( data, parent ) { + + // console.log( data ); + + if ( typeof data === 'string' ) { + + if ( /USE/.exec( data ) ) { + + var defineKey = /USE\s+?(\w+)/.exec( data )[ 1 ]; + + if (undefined == defines[defineKey]) { + console.warn(defineKey + ' is not defined.'); + } else { + + if ( /appearance/.exec( data ) && defineKey ) { + + parent.material = defines[ defineKey ].clone(); + + } else if ( /geometry/.exec( data ) && defineKey ) { + + parent.geometry = defines[ defineKey ].clone(); + + // the solid property is not cloned with clone(), is only needed for VRML loading, so we need to transfer it + if (undefined !== defines[ defineKey ].solid && defines[ defineKey ].solid === false) { + parent.geometry.solid = false; + parent.material.side = THREE.DoubleSide; + } + + } else if (defineKey){ + + var object = defines[ defineKey ].clone(); + parent.add( object ); + + } + + } + + } + + return; + + } + + var object = parent; + + if ( 'Transform' === data.nodeType || 'Group' === data.nodeType ) { + + object = new THREE.Object3D(); + + if ( /DEF/.exec( data.string ) ) { + object.name = /DEF\s+(\w+)/.exec( data.string )[ 1 ]; + defines[ object.name ] = object; + } + + if ( undefined !== data['translation'] ) { + + var t = data.translation; + + object.position.set(t.x, t.y, t.z); + + } + + if ( undefined !== data.rotation ) { + + var r = data.rotation; + + object.quaternion.setFromAxisAngle( new THREE.Vector3( r.x, r.y, r.z ), r.w ); + + } + + if ( undefined !== data.scale ) { + + var s = data.scale; + + object.scale.set( s.x, s.y, s.z ); + + } + + parent.add( object ); + + } else if ( 'Shape' === data.nodeType ) { + + object = new THREE.Mesh(); + + if ( /DEF/.exec( data.string ) ) { + + object.name = /DEF (\w+)/.exec( data.string )[ 1 ]; + + defines[ object.name ] = object; + } + + parent.add( object ); + + } else if ( 'Background' === data.nodeType ) { + + var segments = 20; + + // sky (full sphere): + + var radius = 2e4; + + var skyGeometry = new THREE.SphereGeometry( radius, segments, segments ); + var skyMaterial = new THREE.MeshBasicMaterial( { fog: false, side: THREE.BackSide } ); + + if ( data.skyColor.length > 1 ) { + + paintFaces( skyGeometry, radius, data.skyAngle, data.skyColor, true ); + + skyMaterial.vertexColors = THREE.VertexColors + + } else { + + var color = data.skyColor[ 0 ]; + skyMaterial.color.setRGB( color.r, color.b, color.g ); + + } + + scene.add( new THREE.Mesh( skyGeometry, skyMaterial ) ); + + // ground (half sphere): + + if ( data.groundColor !== undefined ) { + + radius = 1.2e4; + + var groundGeometry = new THREE.SphereGeometry( radius, segments, segments, 0, 2 * Math.PI, 0.5 * Math.PI, 1.5 * Math.PI ); + var groundMaterial = new THREE.MeshBasicMaterial( { fog: false, side: THREE.BackSide, vertexColors: THREE.VertexColors } ); + + paintFaces( groundGeometry, radius, data.groundAngle, data.groundColor, false ); + + scene.add( new THREE.Mesh( groundGeometry, groundMaterial ) ); + + } + + } else if ( /geometry/.exec( data.string ) ) { + + if ( 'Box' === data.nodeType ) { + + var s = data.size; + + parent.geometry = new THREE.BoxGeometry( s.x, s.y, s.z ); + + } else if ( 'Cylinder' === data.nodeType ) { + + parent.geometry = new THREE.CylinderGeometry( data.radius, data.radius, data.height ); + + } else if ( 'Cone' === data.nodeType ) { + + parent.geometry = new THREE.CylinderGeometry( data.topRadius, data.bottomRadius, data.height ); + + } else if ( 'Sphere' === data.nodeType ) { + + parent.geometry = new THREE.SphereGeometry( data.radius ); + + } else if ( 'IndexedFaceSet' === data.nodeType ) { + + var geometry = new THREE.Geometry(); + + var indexes; + + for ( var i = 0, j = data.children.length; i < j; i++ ) { + + var child = data.children[ i ]; + + var vec; + + if ( 'Coordinate' === child.nodeType ) { + + for ( var k = 0, l = child.points.length; k < l; k++ ) { + + var point = child.points[ k ]; + + vec = new THREE.Vector3( point.x, point.y, point.z ); + + geometry.vertices.push( vec ); + } + + break; + } + } + + var skip = 0; + + // read this: http://math.hws.edu/eck/cs424/notes2013/16_Threejs_Advanced.html + for ( var i = 0, j = data.coordIndex.length; i < j; i++ ) { + + indexes = data.coordIndex[i]; + + // vrml support multipoint indexed face sets (more then 3 vertices). You must calculate the composing triangles here + skip = 0; + + // todo: this is the time to check if the faces are ordered ccw or not (cw) + + // Face3 only works with triangles, but IndexedFaceSet allows shapes with more then three vertices, build them of triangles + while ( indexes.length >= 3 && skip < ( indexes.length -2 ) ) { + + var face = new THREE.Face3( + indexes[0], + indexes[skip + 1], + indexes[skip + 2], + null // normal, will be added later + // todo: pass in the color, if a color index is present + ); + + skip++; + + geometry.faces.push( face ); + + } + + + } + + if ( false === data.solid ) { + parent.material.side = THREE.DoubleSide; + } + + // we need to store it on the geometry for use with defines + geometry.solid = data.solid; + + geometry.computeFaceNormals(); + //geometry.computeVertexNormals(); // does not show + geometry.computeBoundingSphere(); + + // see if it's a define + if ( /DEF/.exec( data.string ) ) { + geometry.name = /DEF (\w+)/.exec( data.string )[ 1 ]; + defines[ geometry.name ] = geometry; + } + + parent.geometry = geometry; + } + + return; + + } else if ( /appearance/.exec( data.string ) ) { + + for ( var i = 0; i < data.children.length; i ++ ) { + + var child = data.children[ i ]; + + if ( 'Material' === child.nodeType ) { + var material = new THREE.MeshPhongMaterial(); + + if ( undefined !== child.diffuseColor ) { + + var d = child.diffuseColor; + + material.color.setRGB( d.r, d.g, d.b ); + + } + + if ( undefined !== child.emissiveColor ) { + + var e = child.emissiveColor; + + material.emissive.setRGB( e.r, e.g, e.b ); + + } + + if ( undefined !== child.specularColor ) { + + var s = child.specularColor; + + material.specular.setRGB( s.r, s.g, s.b ); + + } + + if ( undefined !== child.transparency ) { + + var t = child.transparency; + + // transparency is opposite of opacity + material.opacity = Math.abs( 1 - t ); + + material.transparent = true; + + } + + if ( /DEF/.exec( data.string ) ) { + + material.name = /DEF (\w+)/.exec( data.string )[ 1 ]; + + defines[ material.name ] = material; + + } + + parent.material = material; + + // material found, stop looping + break; + } + + } + + return; + + } + + for ( var i = 0, l = data.children.length; i < l; i ++ ) { + + var child = data.children[ i ]; + + parseNode( data.children[ i ], object ); + + } + + } + + parseNode( getTree( lines ), scene ); + + }; + + var scene = new THREE.Scene(); + + var lines = data.split( '\n' ); + + var header = lines.shift(); + + if ( /V1.0/.exec( header ) ) { + + parseV1( lines, scene ); + + } else if ( /V2.0/.exec( header ) ) { + + parseV2( lines, scene ); + + } + + return scene; + + } + +}; + +THREE.EventDispatcher.prototype.apply( THREE.VRMLLoader.prototype ); + diff --git a/pychaste/src/py/chaste/external/plotting_script.js b/pychaste/src/py/chaste/external/plotting_script.js new file mode 100644 index 0000000000..5f0a7819a1 --- /dev/null +++ b/pychaste/src/py/chaste/external/plotting_script.js @@ -0,0 +1,52 @@ +function pychaste_plot(three_container, file_name, width, height) { + + var renderer; + if (Detector.webgl) + renderer = new THREE.WebGLRenderer( {antialias:true} ); + else + renderer = new THREE.CanvasRenderer(); + + renderer.setSize(width, height); + var canvas = renderer.domElement; + three_container.appendChild(canvas); + canvas.style.cursor = "move"; + + var camera, controls, scene; + + camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.01, 1e10 ); + camera.position.z = 6; + + controls = new THREE.OrbitControls( camera ); + controls.rotateSpeed = 5.0; + controls.zoomSpeed = 5; + controls.noZoom = false; + controls.noPan = false; + + scene = new THREE.Scene(); + scene.add( camera ); + + // light + var dirLight = new THREE.DirectionalLight( 0xffffff ); + dirLight.position.set( 200, 200, 1000 ).normalize(); + camera.add( dirLight ); + camera.add( dirLight.target); + + var loader = new THREE.VRMLLoader(); + loader.load(file_name, function(object){ scene.add(object)}); + + // add a small amount of background ambient light + var ambientLight = new THREE.AmbientLight(0x444444); + scene.add(ambientLight); + + function animate() { + if ( camera instanceof THREE.Camera === false || ! document.body.contains(three_container)) { + console.log("Animation loop failed: stopping"); + return; + } + requestAnimationFrame( animate ); + controls.update(); + renderer.render( scene, camera ); + } + + animate(); +} diff --git a/pychaste/src/py/chaste/external/three.min.js b/pychaste/src/py/chaste/external/three.min.js new file mode 100644 index 0000000000..a88b4afa77 --- /dev/null +++ b/pychaste/src/py/chaste/external/three.min.js @@ -0,0 +1,814 @@ +// threejs.org/license +'use strict';var THREE={REVISION:"69"};"object"===typeof module&&(module.exports=THREE);void 0===Math.sign&&(Math.sign=function(a){return 0>a?-1:0>16&255)/255;this.g=(a>>8&255)/255;this.b=(a&255)/255;return this},setRGB:function(a,b,c){this.r=a;this.g=b;this.b=c;return this},setHSL:function(a,b,c){if(0===b)this.r=this.g=this.b=c;else{var d=function(a,b,c){0>c&&(c+=1);1c?b:c<2/3?a+6*(b-a)*(2/3-c):a};b=.5>=c?c*(1+b):c+b-c*b;c=2*c-b;this.r=d(c,b,a+1/3);this.g=d(c,b,a);this.b=d(c,b,a-1/3)}return this},setStyle:function(a){if(/^rgb\((\d+), ?(\d+), ?(\d+)\)$/i.test(a))return a=/^rgb\((\d+), ?(\d+), ?(\d+)\)$/i.exec(a),this.r=Math.min(255,parseInt(a[1],10))/255,this.g=Math.min(255,parseInt(a[2],10))/255,this.b=Math.min(255,parseInt(a[3],10))/255,this;if(/^rgb\((\d+)\%, ?(\d+)\%, ?(\d+)\%\)$/i.test(a))return a=/^rgb\((\d+)\%, ?(\d+)\%, ?(\d+)\%\)$/i.exec(a),this.r= +Math.min(100,parseInt(a[1],10))/100,this.g=Math.min(100,parseInt(a[2],10))/100,this.b=Math.min(100,parseInt(a[3],10))/100,this;if(/^\#([0-9a-f]{6})$/i.test(a))return a=/^\#([0-9a-f]{6})$/i.exec(a),this.setHex(parseInt(a[1],16)),this;if(/^\#([0-9a-f])([0-9a-f])([0-9a-f])$/i.test(a))return a=/^\#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec(a),this.setHex(parseInt(a[1]+a[1]+a[2]+a[2]+a[3]+a[3],16)),this;if(/^(\w+)$/i.test(a))return this.setHex(THREE.ColorKeywords[a]),this},copy:function(a){this.r=a.r;this.g= +a.g;this.b=a.b;return this},copyGammaToLinear:function(a){this.r=a.r*a.r;this.g=a.g*a.g;this.b=a.b*a.b;return this},copyLinearToGamma:function(a){this.r=Math.sqrt(a.r);this.g=Math.sqrt(a.g);this.b=Math.sqrt(a.b);return this},convertGammaToLinear:function(){var a=this.r,b=this.g,c=this.b;this.r=a*a;this.g=b*b;this.b=c*c;return this},convertLinearToGamma:function(){this.r=Math.sqrt(this.r);this.g=Math.sqrt(this.g);this.b=Math.sqrt(this.b);return this},getHex:function(){return 255*this.r<<16^255*this.g<< +8^255*this.b<<0},getHexString:function(){return("000000"+this.getHex().toString(16)).slice(-6)},getHSL:function(a){a=a||{h:0,s:0,l:0};var b=this.r,c=this.g,d=this.b,e=Math.max(b,c,d),f=Math.min(b,c,d),g,h=(f+e)/2;if(f===e)f=g=0;else{var k=e-f,f=.5>=h?k/(e+f):k/(2-e-f);switch(e){case b:g=(c-d)/k+(cf&&c>b?(c=2*Math.sqrt(1+c-f-b),this._w=(k-g)/c,this._x=.25*c,this._y=(a+e)/c,this._z=(d+h)/c):f>b?(c=2*Math.sqrt(1+f-c-b),this._w=(d-h)/c,this._x=(a+e)/c,this._y= +.25*c,this._z=(g+k)/c):(c=2*Math.sqrt(1+b-c-f),this._w=(e-a)/c,this._x=(d+h)/c,this._y=(g+k)/c,this._z=.25*c);this.onChangeCallback();return this},setFromUnitVectors:function(){var a,b;return function(c,d){void 0===a&&(a=new THREE.Vector3);b=c.dot(d)+1;1E-6>b?(b=0,Math.abs(c.x)>Math.abs(c.z)?a.set(-c.y,c.x,0):a.set(0,-c.z,c.y)):a.crossVectors(c,d);this._x=a.x;this._y=a.y;this._z=a.z;this._w=b;this.normalize();return this}}(),inverse:function(){this.conjugate().normalize();return this},conjugate:function(){this._x*= +-1;this._y*=-1;this._z*=-1;this.onChangeCallback();return this},dot:function(a){return this._x*a._x+this._y*a._y+this._z*a._z+this._w*a._w},lengthSq:function(){return this._x*this._x+this._y*this._y+this._z*this._z+this._w*this._w},length:function(){return Math.sqrt(this._x*this._x+this._y*this._y+this._z*this._z+this._w*this._w)},normalize:function(){var a=this.length();0===a?(this._z=this._y=this._x=0,this._w=1):(a=1/a,this._x*=a,this._y*=a,this._z*=a,this._w*=a);this.onChangeCallback();return this}, +multiply:function(a,b){return void 0!==b?(console.warn("THREE.Quaternion: .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead."),this.multiplyQuaternions(a,b)):this.multiplyQuaternions(this,a)},multiplyQuaternions:function(a,b){var c=a._x,d=a._y,e=a._z,f=a._w,g=b._x,h=b._y,k=b._z,n=b._w;this._x=c*n+f*g+d*k-e*h;this._y=d*n+f*h+e*g-c*k;this._z=e*n+f*k+c*h-d*g;this._w=f*n-c*g-d*h-e*k;this.onChangeCallback();return this},multiplyVector3:function(a){console.warn("THREE.Quaternion: .multiplyVector3() has been removed. Use is now vector.applyQuaternion( quaternion ) instead."); +return a.applyQuaternion(this)},slerp:function(a,b){if(0===b)return this;if(1===b)return this.copy(a);var c=this._x,d=this._y,e=this._z,f=this._w,g=f*a._w+c*a._x+d*a._y+e*a._z;0>g?(this._w=-a._w,this._x=-a._x,this._y=-a._y,this._z=-a._z,g=-g):this.copy(a);if(1<=g)return this._w=f,this._x=c,this._y=d,this._z=e,this;var h=Math.acos(g),k=Math.sqrt(1-g*g);if(.001>Math.abs(k))return this._w=.5*(f+this._w),this._x=.5*(c+this._x),this._y=.5*(d+this._y),this._z=.5*(e+this._z),this;g=Math.sin((1-b)*h)/k;h= +Math.sin(b*h)/k;this._w=f*g+this._w*h;this._x=c*g+this._x*h;this._y=d*g+this._y*h;this._z=e*g+this._z*h;this.onChangeCallback();return this},equals:function(a){return a._x===this._x&&a._y===this._y&&a._z===this._z&&a._w===this._w},fromArray:function(a,b){void 0===b&&(b=0);this._x=a[b];this._y=a[b+1];this._z=a[b+2];this._w=a[b+3];this.onChangeCallback();return this},toArray:function(a,b){void 0===a&&(a=[]);void 0===b&&(b=0);a[b]=this._x;a[b+1]=this._y;a[b+2]=this._z;a[b+3]=this._w;return a},onChange:function(a){this.onChangeCallback= +a;return this},onChangeCallback:function(){},clone:function(){return new THREE.Quaternion(this._x,this._y,this._z,this._w)}};THREE.Quaternion.slerp=function(a,b,c,d){return c.copy(a).slerp(b,d)};THREE.Vector2=function(a,b){this.x=a||0;this.y=b||0}; +THREE.Vector2.prototype={constructor:THREE.Vector2,set:function(a,b){this.x=a;this.y=b;return this},setX:function(a){this.x=a;return this},setY:function(a){this.y=a;return this},setComponent:function(a,b){switch(a){case 0:this.x=b;break;case 1:this.y=b;break;default:throw Error("index is out of range: "+a);}},getComponent:function(a){switch(a){case 0:return this.x;case 1:return this.y;default:throw Error("index is out of range: "+a);}},copy:function(a){this.x=a.x;this.y=a.y;return this},add:function(a, +b){if(void 0!==b)return console.warn("THREE.Vector2: .add() now only accepts one argument. Use .addVectors( a, b ) instead."),this.addVectors(a,b);this.x+=a.x;this.y+=a.y;return this},addVectors:function(a,b){this.x=a.x+b.x;this.y=a.y+b.y;return this},addScalar:function(a){this.x+=a;this.y+=a;return this},sub:function(a,b){if(void 0!==b)return console.warn("THREE.Vector2: .sub() now only accepts one argument. Use .subVectors( a, b ) instead."),this.subVectors(a,b);this.x-=a.x;this.y-=a.y;return this}, +subVectors:function(a,b){this.x=a.x-b.x;this.y=a.y-b.y;return this},multiply:function(a){this.x*=a.x;this.y*=a.y;return this},multiplyScalar:function(a){this.x*=a;this.y*=a;return this},divide:function(a){this.x/=a.x;this.y/=a.y;return this},divideScalar:function(a){0!==a?(a=1/a,this.x*=a,this.y*=a):this.y=this.x=0;return this},min:function(a){this.x>a.x&&(this.x=a.x);this.y>a.y&&(this.y=a.y);return this},max:function(a){this.xb.x&&(this.x=b.x);this.yb.y&&(this.y=b.y);return this},clampScalar:function(){var a,b;return function(c,d){void 0===a&&(a=new THREE.Vector2,b=new THREE.Vector2);a.set(c,c);b.set(d,d);return this.clamp(a,b)}}(),floor:function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y);return this},ceil:function(){this.x=Math.ceil(this.x);this.y=Math.ceil(this.y);return this},round:function(){this.x=Math.round(this.x);this.y=Math.round(this.y);return this}, +roundToZero:function(){this.x=0>this.x?Math.ceil(this.x):Math.floor(this.x);this.y=0>this.y?Math.ceil(this.y):Math.floor(this.y);return this},negate:function(){this.x=-this.x;this.y=-this.y;return this},dot:function(a){return this.x*a.x+this.y*a.y},lengthSq:function(){return this.x*this.x+this.y*this.y},length:function(){return Math.sqrt(this.x*this.x+this.y*this.y)},normalize:function(){return this.divideScalar(this.length())},distanceTo:function(a){return Math.sqrt(this.distanceToSquared(a))},distanceToSquared:function(a){var b= +this.x-a.x;a=this.y-a.y;return b*b+a*a},setLength:function(a){var b=this.length();0!==b&&a!==b&&this.multiplyScalar(a/b);return this},lerp:function(a,b){this.x+=(a.x-this.x)*b;this.y+=(a.y-this.y)*b;return this},equals:function(a){return a.x===this.x&&a.y===this.y},fromArray:function(a,b){void 0===b&&(b=0);this.x=a[b];this.y=a[b+1];return this},toArray:function(a,b){void 0===a&&(a=[]);void 0===b&&(b=0);a[b]=this.x;a[b+1]=this.y;return a},clone:function(){return new THREE.Vector2(this.x,this.y)}}; +THREE.Vector3=function(a,b,c){this.x=a||0;this.y=b||0;this.z=c||0}; +THREE.Vector3.prototype={constructor:THREE.Vector3,set:function(a,b,c){this.x=a;this.y=b;this.z=c;return this},setX:function(a){this.x=a;return this},setY:function(a){this.y=a;return this},setZ:function(a){this.z=a;return this},setComponent:function(a,b){switch(a){case 0:this.x=b;break;case 1:this.y=b;break;case 2:this.z=b;break;default:throw Error("index is out of range: "+a);}},getComponent:function(a){switch(a){case 0:return this.x;case 1:return this.y;case 2:return this.z;default:throw Error("index is out of range: "+ +a);}},copy:function(a){this.x=a.x;this.y=a.y;this.z=a.z;return this},add:function(a,b){if(void 0!==b)return console.warn("THREE.Vector3: .add() now only accepts one argument. Use .addVectors( a, b ) instead."),this.addVectors(a,b);this.x+=a.x;this.y+=a.y;this.z+=a.z;return this},addScalar:function(a){this.x+=a;this.y+=a;this.z+=a;return this},addVectors:function(a,b){this.x=a.x+b.x;this.y=a.y+b.y;this.z=a.z+b.z;return this},sub:function(a,b){if(void 0!==b)return console.warn("THREE.Vector3: .sub() now only accepts one argument. Use .subVectors( a, b ) instead."), +this.subVectors(a,b);this.x-=a.x;this.y-=a.y;this.z-=a.z;return this},subVectors:function(a,b){this.x=a.x-b.x;this.y=a.y-b.y;this.z=a.z-b.z;return this},multiply:function(a,b){if(void 0!==b)return console.warn("THREE.Vector3: .multiply() now only accepts one argument. Use .multiplyVectors( a, b ) instead."),this.multiplyVectors(a,b);this.x*=a.x;this.y*=a.y;this.z*=a.z;return this},multiplyScalar:function(a){this.x*=a;this.y*=a;this.z*=a;return this},multiplyVectors:function(a,b){this.x=a.x*b.x;this.y= +a.y*b.y;this.z=a.z*b.z;return this},applyEuler:function(){var a;return function(b){!1===b instanceof THREE.Euler&&console.error("THREE.Vector3: .applyEuler() now expects a Euler rotation rather than a Vector3 and order.");void 0===a&&(a=new THREE.Quaternion);this.applyQuaternion(a.setFromEuler(b));return this}}(),applyAxisAngle:function(){var a;return function(b,c){void 0===a&&(a=new THREE.Quaternion);this.applyQuaternion(a.setFromAxisAngle(b,c));return this}}(),applyMatrix3:function(a){var b=this.x, +c=this.y,d=this.z;a=a.elements;this.x=a[0]*b+a[3]*c+a[6]*d;this.y=a[1]*b+a[4]*c+a[7]*d;this.z=a[2]*b+a[5]*c+a[8]*d;return this},applyMatrix4:function(a){var b=this.x,c=this.y,d=this.z;a=a.elements;this.x=a[0]*b+a[4]*c+a[8]*d+a[12];this.y=a[1]*b+a[5]*c+a[9]*d+a[13];this.z=a[2]*b+a[6]*c+a[10]*d+a[14];return this},applyProjection:function(a){var b=this.x,c=this.y,d=this.z;a=a.elements;var e=1/(a[3]*b+a[7]*c+a[11]*d+a[15]);this.x=(a[0]*b+a[4]*c+a[8]*d+a[12])*e;this.y=(a[1]*b+a[5]*c+a[9]*d+a[13])*e;this.z= +(a[2]*b+a[6]*c+a[10]*d+a[14])*e;return this},applyQuaternion:function(a){var b=this.x,c=this.y,d=this.z,e=a.x,f=a.y,g=a.z;a=a.w;var h=a*b+f*d-g*c,k=a*c+g*b-e*d,n=a*d+e*c-f*b,b=-e*b-f*c-g*d;this.x=h*a+b*-e+k*-g-n*-f;this.y=k*a+b*-f+n*-e-h*-g;this.z=n*a+b*-g+h*-f-k*-e;return this},project:function(){var a;return function(b){void 0===a&&(a=new THREE.Matrix4);a.multiplyMatrices(b.projectionMatrix,a.getInverse(b.matrixWorld));return this.applyProjection(a)}}(),unproject:function(){var a;return function(b){void 0=== +a&&(a=new THREE.Matrix4);a.multiplyMatrices(b.matrixWorld,a.getInverse(b.projectionMatrix));return this.applyProjection(a)}}(),transformDirection:function(a){var b=this.x,c=this.y,d=this.z;a=a.elements;this.x=a[0]*b+a[4]*c+a[8]*d;this.y=a[1]*b+a[5]*c+a[9]*d;this.z=a[2]*b+a[6]*c+a[10]*d;this.normalize();return this},divide:function(a){this.x/=a.x;this.y/=a.y;this.z/=a.z;return this},divideScalar:function(a){0!==a?(a=1/a,this.x*=a,this.y*=a,this.z*=a):this.z=this.y=this.x=0;return this},min:function(a){this.x> +a.x&&(this.x=a.x);this.y>a.y&&(this.y=a.y);this.z>a.z&&(this.z=a.z);return this},max:function(a){this.xb.x&&(this.x=b.x);this.yb.y&&(this.y=b.y);this.zb.z&&(this.z=b.z);return this},clampScalar:function(){var a,b;return function(c,d){void 0===a&&(a=new THREE.Vector3,b=new THREE.Vector3);a.set(c,c,c);b.set(d,d,d);return this.clamp(a, +b)}}(),floor:function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y);this.z=Math.floor(this.z);return this},ceil:function(){this.x=Math.ceil(this.x);this.y=Math.ceil(this.y);this.z=Math.ceil(this.z);return this},round:function(){this.x=Math.round(this.x);this.y=Math.round(this.y);this.z=Math.round(this.z);return this},roundToZero:function(){this.x=0>this.x?Math.ceil(this.x):Math.floor(this.x);this.y=0>this.y?Math.ceil(this.y):Math.floor(this.y);this.z=0>this.z?Math.ceil(this.z):Math.floor(this.z); +return this},negate:function(){this.x=-this.x;this.y=-this.y;this.z=-this.z;return this},dot:function(a){return this.x*a.x+this.y*a.y+this.z*a.z},lengthSq:function(){return this.x*this.x+this.y*this.y+this.z*this.z},length:function(){return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z)},lengthManhattan:function(){return Math.abs(this.x)+Math.abs(this.y)+Math.abs(this.z)},normalize:function(){return this.divideScalar(this.length())},setLength:function(a){var b=this.length();0!==b&&a!==b&&this.multiplyScalar(a/ +b);return this},lerp:function(a,b){this.x+=(a.x-this.x)*b;this.y+=(a.y-this.y)*b;this.z+=(a.z-this.z)*b;return this},cross:function(a,b){if(void 0!==b)return console.warn("THREE.Vector3: .cross() now only accepts one argument. Use .crossVectors( a, b ) instead."),this.crossVectors(a,b);var c=this.x,d=this.y,e=this.z;this.x=d*a.z-e*a.y;this.y=e*a.x-c*a.z;this.z=c*a.y-d*a.x;return this},crossVectors:function(a,b){var c=a.x,d=a.y,e=a.z,f=b.x,g=b.y,h=b.z;this.x=d*h-e*g;this.y=e*f-c*h;this.z=c*g-d*f;return this}, +projectOnVector:function(){var a,b;return function(c){void 0===a&&(a=new THREE.Vector3);a.copy(c).normalize();b=this.dot(a);return this.copy(a).multiplyScalar(b)}}(),projectOnPlane:function(){var a;return function(b){void 0===a&&(a=new THREE.Vector3);a.copy(this).projectOnVector(b);return this.sub(a)}}(),reflect:function(){var a;return function(b){void 0===a&&(a=new THREE.Vector3);return this.sub(a.copy(b).multiplyScalar(2*this.dot(b)))}}(),angleTo:function(a){a=this.dot(a)/(this.length()*a.length()); +return Math.acos(THREE.Math.clamp(a,-1,1))},distanceTo:function(a){return Math.sqrt(this.distanceToSquared(a))},distanceToSquared:function(a){var b=this.x-a.x,c=this.y-a.y;a=this.z-a.z;return b*b+c*c+a*a},setEulerFromRotationMatrix:function(a,b){console.error("THREE.Vector3: .setEulerFromRotationMatrix() has been removed. Use Euler.setFromRotationMatrix() instead.")},setEulerFromQuaternion:function(a,b){console.error("THREE.Vector3: .setEulerFromQuaternion() has been removed. Use Euler.setFromQuaternion() instead.")}, +getPositionFromMatrix:function(a){console.warn("THREE.Vector3: .getPositionFromMatrix() has been renamed to .setFromMatrixPosition().");return this.setFromMatrixPosition(a)},getScaleFromMatrix:function(a){console.warn("THREE.Vector3: .getScaleFromMatrix() has been renamed to .setFromMatrixScale().");return this.setFromMatrixScale(a)},getColumnFromMatrix:function(a,b){console.warn("THREE.Vector3: .getColumnFromMatrix() has been renamed to .setFromMatrixColumn().");return this.setFromMatrixColumn(a, +b)},setFromMatrixPosition:function(a){this.x=a.elements[12];this.y=a.elements[13];this.z=a.elements[14];return this},setFromMatrixScale:function(a){var b=this.set(a.elements[0],a.elements[1],a.elements[2]).length(),c=this.set(a.elements[4],a.elements[5],a.elements[6]).length();a=this.set(a.elements[8],a.elements[9],a.elements[10]).length();this.x=b;this.y=c;this.z=a;return this},setFromMatrixColumn:function(a,b){var c=4*a,d=b.elements;this.x=d[c];this.y=d[c+1];this.z=d[c+2];return this},equals:function(a){return a.x=== +this.x&&a.y===this.y&&a.z===this.z},fromArray:function(a,b){void 0===b&&(b=0);this.x=a[b];this.y=a[b+1];this.z=a[b+2];return this},toArray:function(a,b){void 0===a&&(a=[]);void 0===b&&(b=0);a[b]=this.x;a[b+1]=this.y;a[b+2]=this.z;return a},clone:function(){return new THREE.Vector3(this.x,this.y,this.z)}};THREE.Vector4=function(a,b,c,d){this.x=a||0;this.y=b||0;this.z=c||0;this.w=void 0!==d?d:1}; +THREE.Vector4.prototype={constructor:THREE.Vector4,set:function(a,b,c,d){this.x=a;this.y=b;this.z=c;this.w=d;return this},setX:function(a){this.x=a;return this},setY:function(a){this.y=a;return this},setZ:function(a){this.z=a;return this},setW:function(a){this.w=a;return this},setComponent:function(a,b){switch(a){case 0:this.x=b;break;case 1:this.y=b;break;case 2:this.z=b;break;case 3:this.w=b;break;default:throw Error("index is out of range: "+a);}},getComponent:function(a){switch(a){case 0:return this.x; +case 1:return this.y;case 2:return this.z;case 3:return this.w;default:throw Error("index is out of range: "+a);}},copy:function(a){this.x=a.x;this.y=a.y;this.z=a.z;this.w=void 0!==a.w?a.w:1;return this},add:function(a,b){if(void 0!==b)return console.warn("THREE.Vector4: .add() now only accepts one argument. Use .addVectors( a, b ) instead."),this.addVectors(a,b);this.x+=a.x;this.y+=a.y;this.z+=a.z;this.w+=a.w;return this},addScalar:function(a){this.x+=a;this.y+=a;this.z+=a;this.w+=a;return this}, +addVectors:function(a,b){this.x=a.x+b.x;this.y=a.y+b.y;this.z=a.z+b.z;this.w=a.w+b.w;return this},sub:function(a,b){if(void 0!==b)return console.warn("THREE.Vector4: .sub() now only accepts one argument. Use .subVectors( a, b ) instead."),this.subVectors(a,b);this.x-=a.x;this.y-=a.y;this.z-=a.z;this.w-=a.w;return this},subVectors:function(a,b){this.x=a.x-b.x;this.y=a.y-b.y;this.z=a.z-b.z;this.w=a.w-b.w;return this},multiplyScalar:function(a){this.x*=a;this.y*=a;this.z*=a;this.w*=a;return this},applyMatrix4:function(a){var b= +this.x,c=this.y,d=this.z,e=this.w;a=a.elements;this.x=a[0]*b+a[4]*c+a[8]*d+a[12]*e;this.y=a[1]*b+a[5]*c+a[9]*d+a[13]*e;this.z=a[2]*b+a[6]*c+a[10]*d+a[14]*e;this.w=a[3]*b+a[7]*c+a[11]*d+a[15]*e;return this},divideScalar:function(a){0!==a?(a=1/a,this.x*=a,this.y*=a,this.z*=a,this.w*=a):(this.z=this.y=this.x=0,this.w=1);return this},setAxisAngleFromQuaternion:function(a){this.w=2*Math.acos(a.w);var b=Math.sqrt(1-a.w*a.w);1E-4>b?(this.x=1,this.z=this.y=0):(this.x=a.x/b,this.y=a.y/b,this.z=a.z/b);return this}, +setAxisAngleFromRotationMatrix:function(a){var b,c,d;a=a.elements;var e=a[0];d=a[4];var f=a[8],g=a[1],h=a[5],k=a[9];c=a[2];b=a[6];var n=a[10];if(.01>Math.abs(d-g)&&.01>Math.abs(f-c)&&.01>Math.abs(k-b)){if(.1>Math.abs(d+g)&&.1>Math.abs(f+c)&&.1>Math.abs(k+b)&&.1>Math.abs(e+h+n-3))return this.set(1,0,0,0),this;a=Math.PI;e=(e+1)/2;h=(h+1)/2;n=(n+1)/2;d=(d+g)/4;f=(f+c)/4;k=(k+b)/4;e>h&&e>n?.01>e?(b=0,d=c=.707106781):(b=Math.sqrt(e),c=d/b,d=f/b):h>n?.01>h?(b=.707106781,c=0,d=.707106781):(c=Math.sqrt(h), +b=d/c,d=k/c):.01>n?(c=b=.707106781,d=0):(d=Math.sqrt(n),b=f/d,c=k/d);this.set(b,c,d,a);return this}a=Math.sqrt((b-k)*(b-k)+(f-c)*(f-c)+(g-d)*(g-d));.001>Math.abs(a)&&(a=1);this.x=(b-k)/a;this.y=(f-c)/a;this.z=(g-d)/a;this.w=Math.acos((e+h+n-1)/2);return this},min:function(a){this.x>a.x&&(this.x=a.x);this.y>a.y&&(this.y=a.y);this.z>a.z&&(this.z=a.z);this.w>a.w&&(this.w=a.w);return this},max:function(a){this.xb.x&&(this.x=b.x);this.yb.y&&(this.y=b.y);this.zb.z&&(this.z=b.z);this.wb.w&&(this.w=b.w);return this},clampScalar:function(){var a,b;return function(c,d){void 0===a&&(a=new THREE.Vector4,b=new THREE.Vector4);a.set(c,c,c,c);b.set(d,d,d,d);return this.clamp(a,b)}}(),floor:function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y);this.z=Math.floor(this.z);this.w=Math.floor(this.w); +return this},ceil:function(){this.x=Math.ceil(this.x);this.y=Math.ceil(this.y);this.z=Math.ceil(this.z);this.w=Math.ceil(this.w);return this},round:function(){this.x=Math.round(this.x);this.y=Math.round(this.y);this.z=Math.round(this.z);this.w=Math.round(this.w);return this},roundToZero:function(){this.x=0>this.x?Math.ceil(this.x):Math.floor(this.x);this.y=0>this.y?Math.ceil(this.y):Math.floor(this.y);this.z=0>this.z?Math.ceil(this.z):Math.floor(this.z);this.w=0>this.w?Math.ceil(this.w):Math.floor(this.w); +return this},negate:function(){this.x=-this.x;this.y=-this.y;this.z=-this.z;this.w=-this.w;return this},dot:function(a){return this.x*a.x+this.y*a.y+this.z*a.z+this.w*a.w},lengthSq:function(){return this.x*this.x+this.y*this.y+this.z*this.z+this.w*this.w},length:function(){return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z+this.w*this.w)},lengthManhattan:function(){return Math.abs(this.x)+Math.abs(this.y)+Math.abs(this.z)+Math.abs(this.w)},normalize:function(){return this.divideScalar(this.length())}, +setLength:function(a){var b=this.length();0!==b&&a!==b&&this.multiplyScalar(a/b);return this},lerp:function(a,b){this.x+=(a.x-this.x)*b;this.y+=(a.y-this.y)*b;this.z+=(a.z-this.z)*b;this.w+=(a.w-this.w)*b;return this},equals:function(a){return a.x===this.x&&a.y===this.y&&a.z===this.z&&a.w===this.w},fromArray:function(a,b){void 0===b&&(b=0);this.x=a[b];this.y=a[b+1];this.z=a[b+2];this.w=a[b+3];return this},toArray:function(a,b){void 0===a&&(a=[]);void 0===b&&(b=0);a[b]=this.x;a[b+1]=this.y;a[b+2]= +this.z;a[b+3]=this.w;return a},clone:function(){return new THREE.Vector4(this.x,this.y,this.z,this.w)}};THREE.Euler=function(a,b,c,d){this._x=a||0;this._y=b||0;this._z=c||0;this._order=d||THREE.Euler.DefaultOrder};THREE.Euler.RotationOrders="XYZ YZX ZXY XZY YXZ ZYX".split(" ");THREE.Euler.DefaultOrder="XYZ"; +THREE.Euler.prototype={constructor:THREE.Euler,_x:0,_y:0,_z:0,_order:THREE.Euler.DefaultOrder,get x(){return this._x},set x(a){this._x=a;this.onChangeCallback()},get y(){return this._y},set y(a){this._y=a;this.onChangeCallback()},get z(){return this._z},set z(a){this._z=a;this.onChangeCallback()},get order(){return this._order},set order(a){this._order=a;this.onChangeCallback()},set:function(a,b,c,d){this._x=a;this._y=b;this._z=c;this._order=d||this._order;this.onChangeCallback();return this},copy:function(a){this._x= +a._x;this._y=a._y;this._z=a._z;this._order=a._order;this.onChangeCallback();return this},setFromRotationMatrix:function(a,b){var c=THREE.Math.clamp,d=a.elements,e=d[0],f=d[4],g=d[8],h=d[1],k=d[5],n=d[9],p=d[2],q=d[6],d=d[10];b=b||this._order;"XYZ"===b?(this._y=Math.asin(c(g,-1,1)),.99999>Math.abs(g)?(this._x=Math.atan2(-n,d),this._z=Math.atan2(-f,e)):(this._x=Math.atan2(q,k),this._z=0)):"YXZ"===b?(this._x=Math.asin(-c(n,-1,1)),.99999>Math.abs(n)?(this._y=Math.atan2(g,d),this._z=Math.atan2(h,k)):(this._y= +Math.atan2(-p,e),this._z=0)):"ZXY"===b?(this._x=Math.asin(c(q,-1,1)),.99999>Math.abs(q)?(this._y=Math.atan2(-p,d),this._z=Math.atan2(-f,k)):(this._y=0,this._z=Math.atan2(h,e))):"ZYX"===b?(this._y=Math.asin(-c(p,-1,1)),.99999>Math.abs(p)?(this._x=Math.atan2(q,d),this._z=Math.atan2(h,e)):(this._x=0,this._z=Math.atan2(-f,k))):"YZX"===b?(this._z=Math.asin(c(h,-1,1)),.99999>Math.abs(h)?(this._x=Math.atan2(-n,k),this._y=Math.atan2(-p,e)):(this._x=0,this._y=Math.atan2(g,d))):"XZY"===b?(this._z=Math.asin(-c(f, +-1,1)),.99999>Math.abs(f)?(this._x=Math.atan2(q,k),this._y=Math.atan2(g,e)):(this._x=Math.atan2(-n,d),this._y=0)):console.warn("THREE.Euler: .setFromRotationMatrix() given unsupported order: "+b);this._order=b;this.onChangeCallback();return this},setFromQuaternion:function(a,b,c){var d=THREE.Math.clamp,e=a.x*a.x,f=a.y*a.y,g=a.z*a.z,h=a.w*a.w;b=b||this._order;"XYZ"===b?(this._x=Math.atan2(2*(a.x*a.w-a.y*a.z),h-e-f+g),this._y=Math.asin(d(2*(a.x*a.z+a.y*a.w),-1,1)),this._z=Math.atan2(2*(a.z*a.w-a.x* +a.y),h+e-f-g)):"YXZ"===b?(this._x=Math.asin(d(2*(a.x*a.w-a.y*a.z),-1,1)),this._y=Math.atan2(2*(a.x*a.z+a.y*a.w),h-e-f+g),this._z=Math.atan2(2*(a.x*a.y+a.z*a.w),h-e+f-g)):"ZXY"===b?(this._x=Math.asin(d(2*(a.x*a.w+a.y*a.z),-1,1)),this._y=Math.atan2(2*(a.y*a.w-a.z*a.x),h-e-f+g),this._z=Math.atan2(2*(a.z*a.w-a.x*a.y),h-e+f-g)):"ZYX"===b?(this._x=Math.atan2(2*(a.x*a.w+a.z*a.y),h-e-f+g),this._y=Math.asin(d(2*(a.y*a.w-a.x*a.z),-1,1)),this._z=Math.atan2(2*(a.x*a.y+a.z*a.w),h+e-f-g)):"YZX"===b?(this._x=Math.atan2(2* +(a.x*a.w-a.z*a.y),h-e+f-g),this._y=Math.atan2(2*(a.y*a.w-a.x*a.z),h+e-f-g),this._z=Math.asin(d(2*(a.x*a.y+a.z*a.w),-1,1))):"XZY"===b?(this._x=Math.atan2(2*(a.x*a.w+a.y*a.z),h-e+f-g),this._y=Math.atan2(2*(a.x*a.z+a.y*a.w),h+e-f-g),this._z=Math.asin(d(2*(a.z*a.w-a.x*a.y),-1,1))):console.warn("THREE.Euler: .setFromQuaternion() given unsupported order: "+b);this._order=b;if(!1!==c)this.onChangeCallback();return this},reorder:function(){var a=new THREE.Quaternion;return function(b){a.setFromEuler(this); +this.setFromQuaternion(a,b)}}(),equals:function(a){return a._x===this._x&&a._y===this._y&&a._z===this._z&&a._order===this._order},fromArray:function(a){this._x=a[0];this._y=a[1];this._z=a[2];void 0!==a[3]&&(this._order=a[3]);this.onChangeCallback();return this},toArray:function(){return[this._x,this._y,this._z,this._order]},onChange:function(a){this.onChangeCallback=a;return this},onChangeCallback:function(){},clone:function(){return new THREE.Euler(this._x,this._y,this._z,this._order)}}; +THREE.Line3=function(a,b){this.start=void 0!==a?a:new THREE.Vector3;this.end=void 0!==b?b:new THREE.Vector3}; +THREE.Line3.prototype={constructor:THREE.Line3,set:function(a,b){this.start.copy(a);this.end.copy(b);return this},copy:function(a){this.start.copy(a.start);this.end.copy(a.end);return this},center:function(a){return(a||new THREE.Vector3).addVectors(this.start,this.end).multiplyScalar(.5)},delta:function(a){return(a||new THREE.Vector3).subVectors(this.end,this.start)},distanceSq:function(){return this.start.distanceToSquared(this.end)},distance:function(){return this.start.distanceTo(this.end)},at:function(a, +b){var c=b||new THREE.Vector3;return this.delta(c).multiplyScalar(a).add(this.start)},closestPointToPointParameter:function(){var a=new THREE.Vector3,b=new THREE.Vector3;return function(c,d){a.subVectors(c,this.start);b.subVectors(this.end,this.start);var e=b.dot(b),e=b.dot(a)/e;d&&(e=THREE.Math.clamp(e,0,1));return e}}(),closestPointToPoint:function(a,b,c){a=this.closestPointToPointParameter(a,b);c=c||new THREE.Vector3;return this.delta(c).multiplyScalar(a).add(this.start)},applyMatrix4:function(a){this.start.applyMatrix4(a); +this.end.applyMatrix4(a);return this},equals:function(a){return a.start.equals(this.start)&&a.end.equals(this.end)},clone:function(){return(new THREE.Line3).copy(this)}};THREE.Box2=function(a,b){this.min=void 0!==a?a:new THREE.Vector2(Infinity,Infinity);this.max=void 0!==b?b:new THREE.Vector2(-Infinity,-Infinity)}; +THREE.Box2.prototype={constructor:THREE.Box2,set:function(a,b){this.min.copy(a);this.max.copy(b);return this},setFromPoints:function(a){this.makeEmpty();for(var b=0,c=a.length;bthis.max.x||a.ythis.max.y?!1:!0},containsBox:function(a){return this.min.x<=a.min.x&&a.max.x<=this.max.x&&this.min.y<=a.min.y&&a.max.y<=this.max.y?!0:!1},getParameter:function(a,b){return(b||new THREE.Vector2).set((a.x-this.min.x)/(this.max.x-this.min.x),(a.y-this.min.y)/(this.max.y-this.min.y))},isIntersectionBox:function(a){return a.max.xthis.max.x||a.max.y +this.max.y?!1:!0},clampPoint:function(a,b){return(b||new THREE.Vector2).copy(a).clamp(this.min,this.max)},distanceToPoint:function(){var a=new THREE.Vector2;return function(b){return a.copy(b).clamp(this.min,this.max).sub(b).length()}}(),intersect:function(a){this.min.max(a.min);this.max.min(a.max);return this},union:function(a){this.min.min(a.min);this.max.max(a.max);return this},translate:function(a){this.min.add(a);this.max.add(a);return this},equals:function(a){return a.min.equals(this.min)&& +a.max.equals(this.max)},clone:function(){return(new THREE.Box2).copy(this)}};THREE.Box3=function(a,b){this.min=void 0!==a?a:new THREE.Vector3(Infinity,Infinity,Infinity);this.max=void 0!==b?b:new THREE.Vector3(-Infinity,-Infinity,-Infinity)}; +THREE.Box3.prototype={constructor:THREE.Box3,set:function(a,b){this.min.copy(a);this.max.copy(b);return this},setFromPoints:function(a){this.makeEmpty();for(var b=0,c=a.length;bthis.max.x||a.ythis.max.y||a.zthis.max.z?!1:!0},containsBox:function(a){return this.min.x<=a.min.x&&a.max.x<=this.max.x&&this.min.y<=a.min.y&&a.max.y<=this.max.y&&this.min.z<=a.min.z&&a.max.z<=this.max.z?!0:!1},getParameter:function(a,b){return(b||new THREE.Vector3).set((a.x-this.min.x)/(this.max.x- +this.min.x),(a.y-this.min.y)/(this.max.y-this.min.y),(a.z-this.min.z)/(this.max.z-this.min.z))},isIntersectionBox:function(a){return a.max.xthis.max.x||a.max.ythis.max.y||a.max.zthis.max.z?!1:!0},clampPoint:function(a,b){return(b||new THREE.Vector3).copy(a).clamp(this.min,this.max)},distanceToPoint:function(){var a=new THREE.Vector3;return function(b){return a.copy(b).clamp(this.min,this.max).sub(b).length()}}(),getBoundingSphere:function(){var a= +new THREE.Vector3;return function(b){b=b||new THREE.Sphere;b.center=this.center();b.radius=.5*this.size(a).length();return b}}(),intersect:function(a){this.min.max(a.min);this.max.min(a.max);return this},union:function(a){this.min.min(a.min);this.max.max(a.max);return this},applyMatrix4:function(){var a=[new THREE.Vector3,new THREE.Vector3,new THREE.Vector3,new THREE.Vector3,new THREE.Vector3,new THREE.Vector3,new THREE.Vector3,new THREE.Vector3];return function(b){a[0].set(this.min.x,this.min.y, +this.min.z).applyMatrix4(b);a[1].set(this.min.x,this.min.y,this.max.z).applyMatrix4(b);a[2].set(this.min.x,this.max.y,this.min.z).applyMatrix4(b);a[3].set(this.min.x,this.max.y,this.max.z).applyMatrix4(b);a[4].set(this.max.x,this.min.y,this.min.z).applyMatrix4(b);a[5].set(this.max.x,this.min.y,this.max.z).applyMatrix4(b);a[6].set(this.max.x,this.max.y,this.min.z).applyMatrix4(b);a[7].set(this.max.x,this.max.y,this.max.z).applyMatrix4(b);this.makeEmpty();this.setFromPoints(a);return this}}(),translate:function(a){this.min.add(a); +this.max.add(a);return this},equals:function(a){return a.min.equals(this.min)&&a.max.equals(this.max)},clone:function(){return(new THREE.Box3).copy(this)}};THREE.Matrix3=function(){this.elements=new Float32Array([1,0,0,0,1,0,0,0,1]);0this.determinant()&&(g=-g);c.x=f[12];c.y=f[13];c.z=f[14];b.elements.set(this.elements);c=1/g;var f=1/h,n=1/k;b.elements[0]*=c;b.elements[1]*= +c;b.elements[2]*=c;b.elements[4]*=f;b.elements[5]*=f;b.elements[6]*=f;b.elements[8]*=n;b.elements[9]*=n;b.elements[10]*=n;d.setFromRotationMatrix(b);e.x=g;e.y=h;e.z=k;return this}}(),makeFrustum:function(a,b,c,d,e,f){var g=this.elements;g[0]=2*e/(b-a);g[4]=0;g[8]=(b+a)/(b-a);g[12]=0;g[1]=0;g[5]=2*e/(d-c);g[9]=(d+c)/(d-c);g[13]=0;g[2]=0;g[6]=0;g[10]=-(f+e)/(f-e);g[14]=-2*f*e/(f-e);g[3]=0;g[7]=0;g[11]=-1;g[15]=0;return this},makePerspective:function(a,b,c,d){a=c*Math.tan(THREE.Math.degToRad(.5*a)); +var e=-a;return this.makeFrustum(e*b,a*b,e,a,c,d)},makeOrthographic:function(a,b,c,d,e,f){var g=this.elements,h=b-a,k=c-d,n=f-e;g[0]=2/h;g[4]=0;g[8]=0;g[12]=-((b+a)/h);g[1]=0;g[5]=2/k;g[9]=0;g[13]=-((c+d)/k);g[2]=0;g[6]=0;g[10]=-2/n;g[14]=-((f+e)/n);g[3]=0;g[7]=0;g[11]=0;g[15]=1;return this},fromArray:function(a){this.elements.set(a);return this},toArray:function(){var a=this.elements;return[a[0],a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8],a[9],a[10],a[11],a[12],a[13],a[14],a[15]]},clone:function(){return(new THREE.Matrix4).fromArray(this.elements)}}; +THREE.Ray=function(a,b){this.origin=void 0!==a?a:new THREE.Vector3;this.direction=void 0!==b?b:new THREE.Vector3}; +THREE.Ray.prototype={constructor:THREE.Ray,set:function(a,b){this.origin.copy(a);this.direction.copy(b);return this},copy:function(a){this.origin.copy(a.origin);this.direction.copy(a.direction);return this},at:function(a,b){return(b||new THREE.Vector3).copy(this.direction).multiplyScalar(a).add(this.origin)},recast:function(){var a=new THREE.Vector3;return function(b){this.origin.copy(this.at(b,a));return this}}(),closestPointToPoint:function(a,b){var c=b||new THREE.Vector3;c.subVectors(a,this.origin); +var d=c.dot(this.direction);return 0>d?c.copy(this.origin):c.copy(this.direction).multiplyScalar(d).add(this.origin)},distanceToPoint:function(){var a=new THREE.Vector3;return function(b){var c=a.subVectors(b,this.origin).dot(this.direction);if(0>c)return this.origin.distanceTo(b);a.copy(this.direction).multiplyScalar(c).add(this.origin);return a.distanceTo(b)}}(),distanceSqToSegment:function(a,b,c,d){var e=a.clone().add(b).multiplyScalar(.5),f=b.clone().sub(a).normalize(),g=.5*a.distanceTo(b),h= +this.origin.clone().sub(e);a=-this.direction.dot(f);b=h.dot(this.direction);var k=-h.dot(f),n=h.lengthSq(),p=Math.abs(1-a*a),q,m;0<=p?(h=a*k-b,q=a*b-k,m=g*p,0<=h?q>=-m?q<=m?(g=1/p,h*=g,q*=g,a=h*(h+a*q+2*b)+q*(a*h+q+2*k)+n):(q=g,h=Math.max(0,-(a*q+b)),a=-h*h+q*(q+2*k)+n):(q=-g,h=Math.max(0,-(a*q+b)),a=-h*h+q*(q+2*k)+n):q<=-m?(h=Math.max(0,-(-a*g+b)),q=0f)return null;f=Math.sqrt(f-e);e=d-f; +d+=f;return 0>e&&0>d?null:0>e?this.at(d,c):this.at(e,c)}}(),isIntersectionPlane:function(a){var b=a.distanceToPoint(this.origin);return 0===b||0>a.normal.dot(this.direction)*b?!0:!1},distanceToPlane:function(a){var b=a.normal.dot(this.direction);if(0==b)return 0==a.distanceToPoint(this.origin)?0:null;a=-(this.origin.dot(a.normal)+a.constant)/b;return 0<=a?a:null},intersectPlane:function(a,b){var c=this.distanceToPlane(a);return null===c?null:this.at(c,b)},isIntersectionBox:function(){var a=new THREE.Vector3; +return function(b){return null!==this.intersectBox(b,a)}}(),intersectBox:function(a,b){var c,d,e,f,g;d=1/this.direction.x;f=1/this.direction.y;g=1/this.direction.z;var h=this.origin;0<=d?(c=(a.min.x-h.x)*d,d*=a.max.x-h.x):(c=(a.max.x-h.x)*d,d*=a.min.x-h.x);0<=f?(e=(a.min.y-h.y)*f,f*=a.max.y-h.y):(e=(a.max.y-h.y)*f,f*=a.min.y-h.y);if(c>f||e>d)return null;if(e>c||c!==c)c=e;if(fg||e>d)return null;if(e>c||c!== +c)c=e;if(gd?null:this.at(0<=c?c:d,b)},intersectTriangle:function(){var a=new THREE.Vector3,b=new THREE.Vector3,c=new THREE.Vector3,d=new THREE.Vector3;return function(e,f,g,h,k){b.subVectors(f,e);c.subVectors(g,e);d.crossVectors(b,c);f=this.direction.dot(d);if(0f)h=-1,f=-f;else return null;a.subVectors(this.origin,e);e=h*this.direction.dot(c.crossVectors(a,c));if(0>e)return null;g=h*this.direction.dot(b.cross(a));if(0>g||e+g>f)return null; +e=-h*a.dot(d);return 0>e?null:this.at(e/f,k)}}(),applyMatrix4:function(a){this.direction.add(this.origin).applyMatrix4(a);this.origin.applyMatrix4(a);this.direction.sub(this.origin);this.direction.normalize();return this},equals:function(a){return a.origin.equals(this.origin)&&a.direction.equals(this.direction)},clone:function(){return(new THREE.Ray).copy(this)}};THREE.Sphere=function(a,b){this.center=void 0!==a?a:new THREE.Vector3;this.radius=void 0!==b?b:0}; +THREE.Sphere.prototype={constructor:THREE.Sphere,set:function(a,b){this.center.copy(a);this.radius=b;return this},setFromPoints:function(){var a=new THREE.Box3;return function(b,c){var d=this.center;void 0!==c?d.copy(c):a.setFromPoints(b).center(d);for(var e=0,f=0,g=b.length;f=this.radius},containsPoint:function(a){return a.distanceToSquared(this.center)<= +this.radius*this.radius},distanceToPoint:function(a){return a.distanceTo(this.center)-this.radius},intersectsSphere:function(a){var b=this.radius+a.radius;return a.center.distanceToSquared(this.center)<=b*b},clampPoint:function(a,b){var c=this.center.distanceToSquared(a),d=b||new THREE.Vector3;d.copy(a);c>this.radius*this.radius&&(d.sub(this.center).normalize(),d.multiplyScalar(this.radius).add(this.center));return d},getBoundingBox:function(a){a=a||new THREE.Box3;a.set(this.center,this.center);a.expandByScalar(this.radius); +return a},applyMatrix4:function(a){this.center.applyMatrix4(a);this.radius*=a.getMaxScaleOnAxis();return this},translate:function(a){this.center.add(a);return this},equals:function(a){return a.center.equals(this.center)&&a.radius===this.radius},clone:function(){return(new THREE.Sphere).copy(this)}}; +THREE.Frustum=function(a,b,c,d,e,f){this.planes=[void 0!==a?a:new THREE.Plane,void 0!==b?b:new THREE.Plane,void 0!==c?c:new THREE.Plane,void 0!==d?d:new THREE.Plane,void 0!==e?e:new THREE.Plane,void 0!==f?f:new THREE.Plane]}; +THREE.Frustum.prototype={constructor:THREE.Frustum,set:function(a,b,c,d,e,f){var g=this.planes;g[0].copy(a);g[1].copy(b);g[2].copy(c);g[3].copy(d);g[4].copy(e);g[5].copy(f);return this},copy:function(a){for(var b=this.planes,c=0;6>c;c++)b[c].copy(a.planes[c]);return this},setFromMatrix:function(a){var b=this.planes,c=a.elements;a=c[0];var d=c[1],e=c[2],f=c[3],g=c[4],h=c[5],k=c[6],n=c[7],p=c[8],q=c[9],m=c[10],r=c[11],t=c[12],s=c[13],u=c[14],c=c[15];b[0].setComponents(f-a,n-g,r-p,c-t).normalize();b[1].setComponents(f+ +a,n+g,r+p,c+t).normalize();b[2].setComponents(f+d,n+h,r+q,c+s).normalize();b[3].setComponents(f-d,n-h,r-q,c-s).normalize();b[4].setComponents(f-e,n-k,r-m,c-u).normalize();b[5].setComponents(f+e,n+k,r+m,c+u).normalize();return this},intersectsObject:function(){var a=new THREE.Sphere;return function(b){var c=b.geometry;null===c.boundingSphere&&c.computeBoundingSphere();a.copy(c.boundingSphere);a.applyMatrix4(b.matrixWorld);return this.intersectsSphere(a)}}(),intersectsSphere:function(a){var b=this.planes, +c=a.center;a=-a.radius;for(var d=0;6>d;d++)if(b[d].distanceToPoint(c)e;e++){var f=d[e];a.x=0g&&0>f)return!1}return!0}}(), +containsPoint:function(a){for(var b=this.planes,c=0;6>c;c++)if(0>b[c].distanceToPoint(a))return!1;return!0},clone:function(){return(new THREE.Frustum).copy(this)}};THREE.Plane=function(a,b){this.normal=void 0!==a?a:new THREE.Vector3(1,0,0);this.constant=void 0!==b?b:0}; +THREE.Plane.prototype={constructor:THREE.Plane,set:function(a,b){this.normal.copy(a);this.constant=b;return this},setComponents:function(a,b,c,d){this.normal.set(a,b,c);this.constant=d;return this},setFromNormalAndCoplanarPoint:function(a,b){this.normal.copy(a);this.constant=-b.dot(this.normal);return this},setFromCoplanarPoints:function(){var a=new THREE.Vector3,b=new THREE.Vector3;return function(c,d,e){d=a.subVectors(e,d).cross(b.subVectors(c,d)).normalize();this.setFromNormalAndCoplanarPoint(d, +c);return this}}(),copy:function(a){this.normal.copy(a.normal);this.constant=a.constant;return this},normalize:function(){var a=1/this.normal.length();this.normal.multiplyScalar(a);this.constant*=a;return this},negate:function(){this.constant*=-1;this.normal.negate();return this},distanceToPoint:function(a){return this.normal.dot(a)+this.constant},distanceToSphere:function(a){return this.distanceToPoint(a.center)-a.radius},projectPoint:function(a,b){return this.orthoPoint(a,b).sub(a).negate()},orthoPoint:function(a, +b){var c=this.distanceToPoint(a);return(b||new THREE.Vector3).copy(this.normal).multiplyScalar(c)},isIntersectionLine:function(a){var b=this.distanceToPoint(a.start);a=this.distanceToPoint(a.end);return 0>b&&0a&&0f||1e;e++)8==e||13==e||18==e||23==e?b[e]="-":14==e?b[e]="4":(2>=c&&(c=33554432+16777216*Math.random()|0),d=c&15,c>>=4,b[e]=a[19==e?d&3|8:d]);return b.join("")}}(),clamp:function(a,b,c){return ac?c:a},clampBottom:function(a,b){return a=c)return 1;a=(a-b)/(c-b);return a*a*(3-2*a)},smootherstep:function(a,b,c){if(a<=b)return 0;if(a>=c)return 1;a=(a-b)/(c-b);return a*a*a*(a*(6*a-15)+10)},random16:function(){return(65280*Math.random()+255*Math.random())/65535},randInt:function(a,b){return a+Math.floor(Math.random()*(b-a+1))},randFloat:function(a,b){return a+Math.random()*(b-a)},randFloatSpread:function(a){return a*(.5-Math.random())},degToRad:function(){var a=Math.PI/180;return function(b){return b*a}}(),radToDeg:function(){var a= +180/Math.PI;return function(b){return b*a}}(),isPowerOfTwo:function(a){return 0===(a&a-1)&&0!==a}}; +THREE.Spline=function(a){function b(a,b,c,d,e,f,g){a=.5*(c-a);d=.5*(d-b);return(2*(b-c)+a+d)*g+(-3*(b-c)-2*a-d)*f+a*e+b}this.points=a;var c=[],d={x:0,y:0,z:0},e,f,g,h,k,n,p,q,m;this.initFromArray=function(a){this.points=[];for(var b=0;bthis.points.length-2?this.points.length-1:f+1;c[3]=f>this.points.length-3?this.points.length-1:f+ +2;n=this.points[c[0]];p=this.points[c[1]];q=this.points[c[2]];m=this.points[c[3]];h=g*g;k=g*h;d.x=b(n.x,p.x,q.x,m.x,g,h,k);d.y=b(n.y,p.y,q.y,m.y,g,h,k);d.z=b(n.z,p.z,q.z,m.z,g,h,k);return d};this.getControlPointsArray=function(){var a,b,c=this.points.length,d=[];for(a=0;a=b.x+b.y}}(); +THREE.Triangle.prototype={constructor:THREE.Triangle,set:function(a,b,c){this.a.copy(a);this.b.copy(b);this.c.copy(c);return this},setFromPointsAndIndices:function(a,b,c,d){this.a.copy(a[b]);this.b.copy(a[c]);this.c.copy(a[d]);return this},copy:function(a){this.a.copy(a.a);this.b.copy(a.b);this.c.copy(a.c);return this},area:function(){var a=new THREE.Vector3,b=new THREE.Vector3;return function(){a.subVectors(this.c,this.b);b.subVectors(this.a,this.b);return.5*a.cross(b).length()}}(),midpoint:function(a){return(a|| +new THREE.Vector3).addVectors(this.a,this.b).add(this.c).multiplyScalar(1/3)},normal:function(a){return THREE.Triangle.normal(this.a,this.b,this.c,a)},plane:function(a){return(a||new THREE.Plane).setFromCoplanarPoints(this.a,this.b,this.c)},barycoordFromPoint:function(a,b){return THREE.Triangle.barycoordFromPoint(a,this.a,this.b,this.c,b)},containsPoint:function(a){return THREE.Triangle.containsPoint(a,this.a,this.b,this.c)},equals:function(a){return a.a.equals(this.a)&&a.b.equals(this.b)&&a.c.equals(this.c)}, +clone:function(){return(new THREE.Triangle).copy(this)}};THREE.Clock=function(a){this.autoStart=void 0!==a?a:!0;this.elapsedTime=this.oldTime=this.startTime=0;this.running=!1}; +THREE.Clock.prototype={constructor:THREE.Clock,start:function(){this.oldTime=this.startTime=void 0!==self.performance&&void 0!==self.performance.now?self.performance.now():Date.now();this.running=!0},stop:function(){this.getElapsedTime();this.running=!1},getElapsedTime:function(){this.getDelta();return this.elapsedTime},getDelta:function(){var a=0;this.autoStart&&!this.running&&this.start();if(this.running){var b=void 0!==self.performance&&void 0!==self.performance.now?self.performance.now():Date.now(), +a=.001*(b-this.oldTime);this.oldTime=b;this.elapsedTime+=a}return a}};THREE.EventDispatcher=function(){}; +THREE.EventDispatcher.prototype={constructor:THREE.EventDispatcher,apply:function(a){a.addEventListener=THREE.EventDispatcher.prototype.addEventListener;a.hasEventListener=THREE.EventDispatcher.prototype.hasEventListener;a.removeEventListener=THREE.EventDispatcher.prototype.removeEventListener;a.dispatchEvent=THREE.EventDispatcher.prototype.dispatchEvent},addEventListener:function(a,b){void 0===this._listeners&&(this._listeners={});var c=this._listeners;void 0===c[a]&&(c[a]=[]);-1===c[a].indexOf(b)&& +c[a].push(b)},hasEventListener:function(a,b){if(void 0===this._listeners)return!1;var c=this._listeners;return void 0!==c[a]&&-1!==c[a].indexOf(b)?!0:!1},removeEventListener:function(a,b){if(void 0!==this._listeners){var c=this._listeners[a];if(void 0!==c){var d=c.indexOf(b);-1!==d&&c.splice(d,1)}}},dispatchEvent:function(a){if(void 0!==this._listeners){var b=this._listeners[a.type];if(void 0!==b){a.target=this;for(var c=[],d=b.length,e=0;eza?-1:1;h[4*a]=la.x;h[4*a+1]=la.y;h[4*a+2]=la.z;h[4*a+3]=Ga}if(void 0===this.attributes.index||void 0===this.attributes.position||void 0===this.attributes.normal||void 0===this.attributes.uv)console.warn("Missing required attributes (index, position, normal or uv) in BufferGeometry.computeTangents()");else{var c=this.attributes.index.array,d=this.attributes.position.array, +e=this.attributes.normal.array,f=this.attributes.uv.array,g=d.length/3;void 0===this.attributes.tangent&&this.addAttribute("tangent",new THREE.BufferAttribute(new Float32Array(4*g),4));for(var h=this.attributes.tangent.array,k=[],n=[],p=0;ps;s++)t=a[3*c+s],-1==m[t]?(q[2*s]=t,q[2*s+1]=-1,p++):m[t]k.index+b)for(k={start:f,count:0,index:g},h.push(k),p=0;6>p;p+=2)s=q[p+1],-1p;p+=2)t=q[p],s=q[p+1],-1===s&&(s=g++),m[t]=s,r[s]=t,e[f++]=s-k.index,k.count++}this.reorderBuffers(e,r,g);return this.offsets=h},merge:function(){console.log("BufferGeometry.merge(): TODO")},normalizeNormals:function(){for(var a=this.attributes.normal.array,b,c,d,e=0,f=a.length;ed?-1:1,e.vertexTangents[c]=new THREE.Vector4(w.x,w.y,w.z,d);this.hasTangents=!0},computeLineDistances:function(){for(var a=0,b=this.vertices,c=0,d=b.length;cd;d++)if(e[d]==e[(d+1)%3]){a.push(f);break}for(f=a.length-1;0<=f;f--)for(e=a[f],this.faces.splice(e,1),c=0,g=this.faceVertexUvs.length;ca.opacity)h.transparent=a.transparent;void 0!==a.depthTest&&(h.depthTest=a.depthTest);void 0!==a.depthWrite&&(h.depthWrite=a.depthWrite);void 0!==a.visible&&(h.visible=a.visible);void 0!==a.flipSided&&(h.side=THREE.BackSide);void 0!==a.doubleSided&&(h.side=THREE.DoubleSide);void 0!==a.wireframe&&(h.wireframe=a.wireframe);void 0!==a.vertexColors&&("face"=== +a.vertexColors?h.vertexColors=THREE.FaceColors:a.vertexColors&&(h.vertexColors=THREE.VertexColors));a.colorDiffuse?h.color=e(a.colorDiffuse):a.DbgColor&&(h.color=a.DbgColor);a.colorSpecular&&(h.specular=e(a.colorSpecular));a.colorAmbient&&(h.ambient=e(a.colorAmbient));a.colorEmissive&&(h.emissive=e(a.colorEmissive));a.transparency&&(h.opacity=a.transparency);a.specularCoef&&(h.shininess=a.specularCoef);a.mapDiffuse&&b&&d(h,"map",a.mapDiffuse,a.mapDiffuseRepeat,a.mapDiffuseOffset,a.mapDiffuseWrap, +a.mapDiffuseAnisotropy);a.mapLight&&b&&d(h,"lightMap",a.mapLight,a.mapLightRepeat,a.mapLightOffset,a.mapLightWrap,a.mapLightAnisotropy);a.mapBump&&b&&d(h,"bumpMap",a.mapBump,a.mapBumpRepeat,a.mapBumpOffset,a.mapBumpWrap,a.mapBumpAnisotropy);a.mapNormal&&b&&d(h,"normalMap",a.mapNormal,a.mapNormalRepeat,a.mapNormalOffset,a.mapNormalWrap,a.mapNormalAnisotropy);a.mapSpecular&&b&&d(h,"specularMap",a.mapSpecular,a.mapSpecularRepeat,a.mapSpecularOffset,a.mapSpecularWrap,a.mapSpecularAnisotropy);a.mapAlpha&& +b&&d(h,"alphaMap",a.mapAlpha,a.mapAlphaRepeat,a.mapAlphaOffset,a.mapAlphaWrap,a.mapAlphaAnisotropy);a.mapBumpScale&&(h.bumpScale=a.mapBumpScale);a.mapNormal?(g=THREE.ShaderLib.normalmap,k=THREE.UniformsUtils.clone(g.uniforms),k.tNormal.value=h.normalMap,a.mapNormalFactor&&k.uNormalScale.value.set(a.mapNormalFactor,a.mapNormalFactor),h.map&&(k.tDiffuse.value=h.map,k.enableDiffuse.value=!0),h.specularMap&&(k.tSpecular.value=h.specularMap,k.enableSpecular.value=!0),h.lightMap&&(k.tAO.value=h.lightMap, +k.enableAO.value=!0),k.diffuse.value.setHex(h.color),k.specular.value.setHex(h.specular),k.ambient.value.setHex(h.ambient),k.shininess.value=h.shininess,void 0!==h.opacity&&(k.opacity.value=h.opacity),g=new THREE.ShaderMaterial({fragmentShader:g.fragmentShader,vertexShader:g.vertexShader,uniforms:k,lights:!0,fog:!0}),h.transparent&&(g.transparent=!0)):g=new THREE[g](h);void 0!==a.DbgName&&(g.name=a.DbgName);return g}}; +THREE.Loader.Handlers={handlers:[],add:function(a,b){this.handlers.push(a,b)},get:function(a){for(var b=0,c=this.handlers.length;bg;g++)m=y[k++],v=u[2*m],m=u[2*m+1],v=new THREE.Vector2(v,m),2!==g&&c.faceVertexUvs[d][h].push(v),0!==g&&c.faceVertexUvs[d][h+1].push(v);q&&(q=3*y[k++],r.normal.set(G[q++],G[q++],G[q]),s.normal.copy(r.normal));if(t)for(d=0;4>d;d++)q=3*y[k++],t=new THREE.Vector3(G[q++], +G[q++],G[q]),2!==d&&r.vertexNormals.push(t),0!==d&&s.vertexNormals.push(t);p&&(p=y[k++],p=w[p],r.color.setHex(p),s.color.setHex(p));if(b)for(d=0;4>d;d++)p=y[k++],p=w[p],2!==d&&r.vertexColors.push(new THREE.Color(p)),0!==d&&s.vertexColors.push(new THREE.Color(p));c.faces.push(r);c.faces.push(s)}else{r=new THREE.Face3;r.a=y[k++];r.b=y[k++];r.c=y[k++];h&&(h=y[k++],r.materialIndex=h);h=c.faces.length;if(d)for(d=0;dg;g++)m=y[k++],v=u[2*m],m=u[2*m+1], +v=new THREE.Vector2(v,m),c.faceVertexUvs[d][h].push(v);q&&(q=3*y[k++],r.normal.set(G[q++],G[q++],G[q]));if(t)for(d=0;3>d;d++)q=3*y[k++],t=new THREE.Vector3(G[q++],G[q++],G[q]),r.vertexNormals.push(t);p&&(p=y[k++],r.color.setHex(w[p]));if(b)for(d=0;3>d;d++)p=y[k++],r.vertexColors.push(new THREE.Color(w[p]));c.faces.push(r)}})(d);(function(){var b=void 0!==a.influencesPerVertex?a.influencesPerVertex:2;if(a.skinWeights)for(var d=0,g=a.skinWeights.length;dthis.opacity&&(a.opacity=this.opacity);!1!==this.transparent&&(a.transparent=this.transparent);!1!==this.wireframe&&(a.wireframe=this.wireframe);return a},clone:function(a){void 0===a&&(a=new THREE.Material);a.name=this.name;a.side=this.side;a.opacity=this.opacity;a.transparent=this.transparent;a.blending=this.blending;a.blendSrc=this.blendSrc;a.blendDst=this.blendDst;a.blendEquation=this.blendEquation;a.depthTest=this.depthTest;a.depthWrite=this.depthWrite;a.polygonOffset=this.polygonOffset;a.polygonOffsetFactor= +this.polygonOffsetFactor;a.polygonOffsetUnits=this.polygonOffsetUnits;a.alphaTest=this.alphaTest;a.overdraw=this.overdraw;a.visible=this.visible;return a},dispose:function(){this.dispatchEvent({type:"dispose"})}};THREE.EventDispatcher.prototype.apply(THREE.Material.prototype);THREE.MaterialIdCount=0; +THREE.LineBasicMaterial=function(a){THREE.Material.call(this);this.type="LineBasicMaterial";this.color=new THREE.Color(16777215);this.linewidth=1;this.linejoin=this.linecap="round";this.vertexColors=THREE.NoColors;this.fog=!0;this.setValues(a)};THREE.LineBasicMaterial.prototype=Object.create(THREE.Material.prototype); +THREE.LineBasicMaterial.prototype.clone=function(){var a=new THREE.LineBasicMaterial;THREE.Material.prototype.clone.call(this,a);a.color.copy(this.color);a.linewidth=this.linewidth;a.linecap=this.linecap;a.linejoin=this.linejoin;a.vertexColors=this.vertexColors;a.fog=this.fog;return a}; +THREE.LineDashedMaterial=function(a){THREE.Material.call(this);this.type="LineDashedMaterial";this.color=new THREE.Color(16777215);this.scale=this.linewidth=1;this.dashSize=3;this.gapSize=1;this.vertexColors=!1;this.fog=!0;this.setValues(a)};THREE.LineDashedMaterial.prototype=Object.create(THREE.Material.prototype); +THREE.LineDashedMaterial.prototype.clone=function(){var a=new THREE.LineDashedMaterial;THREE.Material.prototype.clone.call(this,a);a.color.copy(this.color);a.linewidth=this.linewidth;a.scale=this.scale;a.dashSize=this.dashSize;a.gapSize=this.gapSize;a.vertexColors=this.vertexColors;a.fog=this.fog;return a}; +THREE.MeshBasicMaterial=function(a){THREE.Material.call(this);this.type="MeshBasicMaterial";this.color=new THREE.Color(16777215);this.envMap=this.alphaMap=this.specularMap=this.lightMap=this.map=null;this.combine=THREE.MultiplyOperation;this.reflectivity=1;this.refractionRatio=.98;this.fog=!0;this.shading=THREE.SmoothShading;this.wireframe=!1;this.wireframeLinewidth=1;this.wireframeLinejoin=this.wireframeLinecap="round";this.vertexColors=THREE.NoColors;this.morphTargets=this.skinning=!1;this.setValues(a)}; +THREE.MeshBasicMaterial.prototype=Object.create(THREE.Material.prototype); +THREE.MeshBasicMaterial.prototype.clone=function(){var a=new THREE.MeshBasicMaterial;THREE.Material.prototype.clone.call(this,a);a.color.copy(this.color);a.map=this.map;a.lightMap=this.lightMap;a.specularMap=this.specularMap;a.alphaMap=this.alphaMap;a.envMap=this.envMap;a.combine=this.combine;a.reflectivity=this.reflectivity;a.refractionRatio=this.refractionRatio;a.fog=this.fog;a.shading=this.shading;a.wireframe=this.wireframe;a.wireframeLinewidth=this.wireframeLinewidth;a.wireframeLinecap=this.wireframeLinecap; +a.wireframeLinejoin=this.wireframeLinejoin;a.vertexColors=this.vertexColors;a.skinning=this.skinning;a.morphTargets=this.morphTargets;return a}; +THREE.MeshLambertMaterial=function(a){THREE.Material.call(this);this.type="MeshLambertMaterial";this.color=new THREE.Color(16777215);this.ambient=new THREE.Color(16777215);this.emissive=new THREE.Color(0);this.wrapAround=!1;this.wrapRGB=new THREE.Vector3(1,1,1);this.envMap=this.alphaMap=this.specularMap=this.lightMap=this.map=null;this.combine=THREE.MultiplyOperation;this.reflectivity=1;this.refractionRatio=.98;this.fog=!0;this.shading=THREE.SmoothShading;this.wireframe=!1;this.wireframeLinewidth= +1;this.wireframeLinejoin=this.wireframeLinecap="round";this.vertexColors=THREE.NoColors;this.morphNormals=this.morphTargets=this.skinning=!1;this.setValues(a)};THREE.MeshLambertMaterial.prototype=Object.create(THREE.Material.prototype); +THREE.MeshLambertMaterial.prototype.clone=function(){var a=new THREE.MeshLambertMaterial;THREE.Material.prototype.clone.call(this,a);a.color.copy(this.color);a.ambient.copy(this.ambient);a.emissive.copy(this.emissive);a.wrapAround=this.wrapAround;a.wrapRGB.copy(this.wrapRGB);a.map=this.map;a.lightMap=this.lightMap;a.specularMap=this.specularMap;a.alphaMap=this.alphaMap;a.envMap=this.envMap;a.combine=this.combine;a.reflectivity=this.reflectivity;a.refractionRatio=this.refractionRatio;a.fog=this.fog; +a.shading=this.shading;a.wireframe=this.wireframe;a.wireframeLinewidth=this.wireframeLinewidth;a.wireframeLinecap=this.wireframeLinecap;a.wireframeLinejoin=this.wireframeLinejoin;a.vertexColors=this.vertexColors;a.skinning=this.skinning;a.morphTargets=this.morphTargets;a.morphNormals=this.morphNormals;return a}; +THREE.MeshPhongMaterial=function(a){THREE.Material.call(this);this.type="MeshPhongMaterial";this.color=new THREE.Color(16777215);this.ambient=new THREE.Color(16777215);this.emissive=new THREE.Color(0);this.specular=new THREE.Color(1118481);this.shininess=30;this.wrapAround=this.metal=!1;this.wrapRGB=new THREE.Vector3(1,1,1);this.bumpMap=this.lightMap=this.map=null;this.bumpScale=1;this.normalMap=null;this.normalScale=new THREE.Vector2(1,1);this.envMap=this.alphaMap=this.specularMap=null;this.combine= +THREE.MultiplyOperation;this.reflectivity=1;this.refractionRatio=.98;this.fog=!0;this.shading=THREE.SmoothShading;this.wireframe=!1;this.wireframeLinewidth=1;this.wireframeLinejoin=this.wireframeLinecap="round";this.vertexColors=THREE.NoColors;this.morphNormals=this.morphTargets=this.skinning=!1;this.setValues(a)};THREE.MeshPhongMaterial.prototype=Object.create(THREE.Material.prototype); +THREE.MeshPhongMaterial.prototype.clone=function(){var a=new THREE.MeshPhongMaterial;THREE.Material.prototype.clone.call(this,a);a.color.copy(this.color);a.ambient.copy(this.ambient);a.emissive.copy(this.emissive);a.specular.copy(this.specular);a.shininess=this.shininess;a.metal=this.metal;a.wrapAround=this.wrapAround;a.wrapRGB.copy(this.wrapRGB);a.map=this.map;a.lightMap=this.lightMap;a.bumpMap=this.bumpMap;a.bumpScale=this.bumpScale;a.normalMap=this.normalMap;a.normalScale.copy(this.normalScale); +a.specularMap=this.specularMap;a.alphaMap=this.alphaMap;a.envMap=this.envMap;a.combine=this.combine;a.reflectivity=this.reflectivity;a.refractionRatio=this.refractionRatio;a.fog=this.fog;a.shading=this.shading;a.wireframe=this.wireframe;a.wireframeLinewidth=this.wireframeLinewidth;a.wireframeLinecap=this.wireframeLinecap;a.wireframeLinejoin=this.wireframeLinejoin;a.vertexColors=this.vertexColors;a.skinning=this.skinning;a.morphTargets=this.morphTargets;a.morphNormals=this.morphNormals;return a}; +THREE.MeshDepthMaterial=function(a){THREE.Material.call(this);this.type="MeshDepthMaterial";this.wireframe=this.morphTargets=!1;this.wireframeLinewidth=1;this.setValues(a)};THREE.MeshDepthMaterial.prototype=Object.create(THREE.Material.prototype);THREE.MeshDepthMaterial.prototype.clone=function(){var a=new THREE.MeshDepthMaterial;THREE.Material.prototype.clone.call(this,a);a.wireframe=this.wireframe;a.wireframeLinewidth=this.wireframeLinewidth;return a}; +THREE.MeshNormalMaterial=function(a){THREE.Material.call(this,a);this.type="MeshNormalMaterial";this.shading=THREE.FlatShading;this.wireframe=!1;this.wireframeLinewidth=1;this.morphTargets=!1;this.setValues(a)};THREE.MeshNormalMaterial.prototype=Object.create(THREE.Material.prototype); +THREE.MeshNormalMaterial.prototype.clone=function(){var a=new THREE.MeshNormalMaterial;THREE.Material.prototype.clone.call(this,a);a.shading=this.shading;a.wireframe=this.wireframe;a.wireframeLinewidth=this.wireframeLinewidth;return a};THREE.MeshFaceMaterial=function(a){this.uuid=THREE.Math.generateUUID();this.type="MeshFaceMaterial";this.materials=a instanceof Array?a:[]}; +THREE.MeshFaceMaterial.prototype={constructor:THREE.MeshFaceMaterial,toJSON:function(){for(var a={metadata:{version:4.2,type:"material",generator:"MaterialExporter"},uuid:this.uuid,type:this.type,materials:[]},b=0,c=this.materials.length;bf)){var m=b.origin.distanceTo(n);md.far||e.push({distance:m,point:k.clone().applyMatrix4(this.matrixWorld),face:null,faceIndex:null,object:this})}}}();THREE.Line.prototype.clone=function(a){void 0===a&&(a=new THREE.Line(this.geometry,this.material,this.mode));THREE.Object3D.prototype.clone.call(this,a);return a}; +THREE.Mesh=function(a,b){THREE.Object3D.call(this);this.type="Mesh";this.geometry=void 0!==a?a:new THREE.Geometry;this.material=void 0!==b?b:new THREE.MeshBasicMaterial({color:16777215*Math.random()});this.updateMorphTargets()};THREE.Mesh.prototype=Object.create(THREE.Object3D.prototype); +THREE.Mesh.prototype.updateMorphTargets=function(){if(void 0!==this.geometry.morphTargets&&0g.far||h.push({distance:x,point:K,face:new THREE.Face3(p,q,m,THREE.Triangle.normal(d,e,f)),faceIndex:null,object:this})}}}else for(s=p.position.array,t=k=0,w=s.length;k +g.far||h.push({distance:x,point:K,face:new THREE.Face3(p,q,m,THREE.Triangle.normal(d,e,f)),faceIndex:null,object:this}))}}else if(k instanceof THREE.Geometry)for(t=this.material instanceof THREE.MeshFaceMaterial,s=!0===t?this.material.materials:null,r=g.precision,u=k.vertices,v=0,y=k.faces.length;vg.far||h.push({distance:x,point:K,face:G,faceIndex:v,object:this}))}}}();THREE.Mesh.prototype.clone=function(a,b){void 0===a&&(a=new THREE.Mesh(this.geometry,this.material));THREE.Object3D.prototype.clone.call(this,a,b);return a};THREE.Bone=function(a){THREE.Object3D.call(this);this.skin=a};THREE.Bone.prototype=Object.create(THREE.Object3D.prototype); +THREE.Skeleton=function(a,b,c){this.useVertexTexture=void 0!==c?c:!0;this.identityMatrix=new THREE.Matrix4;a=a||[];this.bones=a.slice(0);this.useVertexTexture?(this.boneTextureHeight=this.boneTextureWidth=a=256h.end&&(h.end=e);b||(b=g)}}a.firstAnimation=b}; +THREE.MorphAnimMesh.prototype.setAnimationLabel=function(a,b,c){this.geometry.animations||(this.geometry.animations={});this.geometry.animations[a]={start:b,end:c}};THREE.MorphAnimMesh.prototype.playAnimation=function(a,b){var c=this.geometry.animations[a];c?(this.setFrameRange(c.start,c.end),this.duration=(c.end-c.start)/b*1E3,this.time=0):console.warn("animation["+a+"] undefined")}; +THREE.MorphAnimMesh.prototype.updateAnimation=function(a){var b=this.duration/this.length;this.time+=this.direction*a;if(this.mirroredLoop){if(this.time>this.duration||0>this.time)this.direction*=-1,this.time>this.duration&&(this.time=this.duration,this.directionBackwards=!0),0>this.time&&(this.time=0,this.directionBackwards=!1)}else this.time%=this.duration,0>this.time&&(this.time+=this.duration);a=this.startKeyframe+THREE.Math.clamp(Math.floor(this.time/b),0,this.length-1);a!==this.currentKeyframe&& +(this.morphTargetInfluences[this.lastKeyframe]=0,this.morphTargetInfluences[this.currentKeyframe]=1,this.morphTargetInfluences[a]=0,this.lastKeyframe=this.currentKeyframe,this.currentKeyframe=a);b=this.time%b/b;this.directionBackwards&&(b=1-b);this.morphTargetInfluences[this.currentKeyframe]=b;this.morphTargetInfluences[this.lastKeyframe]=1-b}; +THREE.MorphAnimMesh.prototype.interpolateTargets=function(a,b,c){for(var d=this.morphTargetInfluences,e=0,f=d.length;e=this.objects[d].distance)this.objects[d-1].object.visible=!1,this.objects[d].object.visible=!0;else break;for(;dthis.scale.x||c.push({distance:d,point:this.position,face:null,object:this})}}();THREE.Sprite.prototype.clone=function(a){void 0===a&&(a=new THREE.Sprite(this.material));THREE.Object3D.prototype.clone.call(this,a);return a};THREE.Particle=THREE.Sprite; +THREE.LensFlare=function(a,b,c,d,e){THREE.Object3D.call(this);this.lensFlares=[];this.positionScreen=new THREE.Vector3;this.customUpdateCallback=void 0;void 0!==a&&this.add(a,b,c,d,e)};THREE.LensFlare.prototype=Object.create(THREE.Object3D.prototype); +THREE.LensFlare.prototype.add=function(a,b,c,d,e,f){void 0===b&&(b=-1);void 0===c&&(c=0);void 0===f&&(f=1);void 0===e&&(e=new THREE.Color(16777215));void 0===d&&(d=THREE.NormalBlending);c=Math.min(c,Math.max(0,c));this.lensFlares.push({texture:a,size:b,distance:c,x:0,y:0,z:0,scale:1,rotation:1,opacity:f,color:e,blending:d})}; +THREE.LensFlare.prototype.updateLensFlares=function(){var a,b=this.lensFlares.length,c,d=2*-this.positionScreen.x,e=2*-this.positionScreen.y;for(a=0;a dashSize ) {\n\t\tdiscard;\n\t}\n\tgl_FragColor = vec4( diffuse, opacity );",THREE.ShaderChunk.logdepthbuf_fragment,THREE.ShaderChunk.color_fragment,THREE.ShaderChunk.fog_fragment, +"}"].join("\n")},depth:{uniforms:{mNear:{type:"f",value:1},mFar:{type:"f",value:2E3},opacity:{type:"f",value:1}},vertexShader:[THREE.ShaderChunk.morphtarget_pars_vertex,THREE.ShaderChunk.logdepthbuf_pars_vertex,"void main() {",THREE.ShaderChunk.morphtarget_vertex,THREE.ShaderChunk.default_vertex,THREE.ShaderChunk.logdepthbuf_vertex,"}"].join("\n"),fragmentShader:["uniform float mNear;\nuniform float mFar;\nuniform float opacity;",THREE.ShaderChunk.logdepthbuf_pars_fragment,"void main() {",THREE.ShaderChunk.logdepthbuf_fragment, +"\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tfloat depth = gl_FragDepthEXT / gl_FragCoord.w;\n\t#else\n\t\tfloat depth = gl_FragCoord.z / gl_FragCoord.w;\n\t#endif\n\tfloat color = 1.0 - smoothstep( mNear, mFar, depth );\n\tgl_FragColor = vec4( vec3( color ), opacity );\n}"].join("\n")},normal:{uniforms:{opacity:{type:"f",value:1}},vertexShader:["varying vec3 vNormal;",THREE.ShaderChunk.morphtarget_pars_vertex,THREE.ShaderChunk.logdepthbuf_pars_vertex,"void main() {\n\tvNormal = normalize( normalMatrix * normal );", +THREE.ShaderChunk.morphtarget_vertex,THREE.ShaderChunk.default_vertex,THREE.ShaderChunk.logdepthbuf_vertex,"}"].join("\n"),fragmentShader:["uniform float opacity;\nvarying vec3 vNormal;",THREE.ShaderChunk.logdepthbuf_pars_fragment,"void main() {\n\tgl_FragColor = vec4( 0.5 * normalize( vNormal ) + 0.5, opacity );",THREE.ShaderChunk.logdepthbuf_fragment,"}"].join("\n")},normalmap:{uniforms:THREE.UniformsUtils.merge([THREE.UniformsLib.fog,THREE.UniformsLib.lights,THREE.UniformsLib.shadowmap,{enableAO:{type:"i", +value:0},enableDiffuse:{type:"i",value:0},enableSpecular:{type:"i",value:0},enableReflection:{type:"i",value:0},enableDisplacement:{type:"i",value:0},tDisplacement:{type:"t",value:null},tDiffuse:{type:"t",value:null},tCube:{type:"t",value:null},tNormal:{type:"t",value:null},tSpecular:{type:"t",value:null},tAO:{type:"t",value:null},uNormalScale:{type:"v2",value:new THREE.Vector2(1,1)},uDisplacementBias:{type:"f",value:0},uDisplacementScale:{type:"f",value:1},diffuse:{type:"c",value:new THREE.Color(16777215)}, +specular:{type:"c",value:new THREE.Color(1118481)},ambient:{type:"c",value:new THREE.Color(16777215)},shininess:{type:"f",value:30},opacity:{type:"f",value:1},useRefract:{type:"i",value:0},refractionRatio:{type:"f",value:.98},reflectivity:{type:"f",value:.5},uOffset:{type:"v2",value:new THREE.Vector2(0,0)},uRepeat:{type:"v2",value:new THREE.Vector2(1,1)},wrapRGB:{type:"v3",value:new THREE.Vector3(1,1,1)}}]),fragmentShader:["uniform vec3 ambient;\nuniform vec3 diffuse;\nuniform vec3 specular;\nuniform float shininess;\nuniform float opacity;\nuniform bool enableDiffuse;\nuniform bool enableSpecular;\nuniform bool enableAO;\nuniform bool enableReflection;\nuniform sampler2D tDiffuse;\nuniform sampler2D tNormal;\nuniform sampler2D tSpecular;\nuniform sampler2D tAO;\nuniform samplerCube tCube;\nuniform vec2 uNormalScale;\nuniform bool useRefract;\nuniform float refractionRatio;\nuniform float reflectivity;\nvarying vec3 vTangent;\nvarying vec3 vBinormal;\nvarying vec3 vNormal;\nvarying vec2 vUv;\nuniform vec3 ambientLightColor;\n#if MAX_DIR_LIGHTS > 0\n\tuniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];\n\tuniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];\n#endif\n#if MAX_HEMI_LIGHTS > 0\n\tuniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];\n\tuniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];\n\tuniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];\n#endif\n#if MAX_POINT_LIGHTS > 0\n\tuniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];\n\tuniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];\n\tuniform float pointLightDistance[ MAX_POINT_LIGHTS ];\n#endif\n#if MAX_SPOT_LIGHTS > 0\n\tuniform vec3 spotLightColor[ MAX_SPOT_LIGHTS ];\n\tuniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];\n\tuniform vec3 spotLightDirection[ MAX_SPOT_LIGHTS ];\n\tuniform float spotLightAngleCos[ MAX_SPOT_LIGHTS ];\n\tuniform float spotLightExponent[ MAX_SPOT_LIGHTS ];\n\tuniform float spotLightDistance[ MAX_SPOT_LIGHTS ];\n#endif\n#ifdef WRAP_AROUND\n\tuniform vec3 wrapRGB;\n#endif\nvarying vec3 vWorldPosition;\nvarying vec3 vViewPosition;", +THREE.ShaderChunk.shadowmap_pars_fragment,THREE.ShaderChunk.fog_pars_fragment,THREE.ShaderChunk.logdepthbuf_pars_fragment,"void main() {",THREE.ShaderChunk.logdepthbuf_fragment,"\tgl_FragColor = vec4( vec3( 1.0 ), opacity );\n\tvec3 specularTex = vec3( 1.0 );\n\tvec3 normalTex = texture2D( tNormal, vUv ).xyz * 2.0 - 1.0;\n\tnormalTex.xy *= uNormalScale;\n\tnormalTex = normalize( normalTex );\n\tif( enableDiffuse ) {\n\t\t#ifdef GAMMA_INPUT\n\t\t\tvec4 texelColor = texture2D( tDiffuse, vUv );\n\t\t\ttexelColor.xyz *= texelColor.xyz;\n\t\t\tgl_FragColor = gl_FragColor * texelColor;\n\t\t#else\n\t\t\tgl_FragColor = gl_FragColor * texture2D( tDiffuse, vUv );\n\t\t#endif\n\t}\n\tif( enableAO ) {\n\t\t#ifdef GAMMA_INPUT\n\t\t\tvec4 aoColor = texture2D( tAO, vUv );\n\t\t\taoColor.xyz *= aoColor.xyz;\n\t\t\tgl_FragColor.xyz = gl_FragColor.xyz * aoColor.xyz;\n\t\t#else\n\t\t\tgl_FragColor.xyz = gl_FragColor.xyz * texture2D( tAO, vUv ).xyz;\n\t\t#endif\n\t}", +THREE.ShaderChunk.alphatest_fragment,"\tif( enableSpecular )\n\t\tspecularTex = texture2D( tSpecular, vUv ).xyz;\n\tmat3 tsb = mat3( normalize( vTangent ), normalize( vBinormal ), normalize( vNormal ) );\n\tvec3 finalNormal = tsb * normalTex;\n\t#ifdef FLIP_SIDED\n\t\tfinalNormal = -finalNormal;\n\t#endif\n\tvec3 normal = normalize( finalNormal );\n\tvec3 viewPosition = normalize( vViewPosition );\n\t#if MAX_POINT_LIGHTS > 0\n\t\tvec3 pointDiffuse = vec3( 0.0 );\n\t\tvec3 pointSpecular = vec3( 0.0 );\n\t\tfor ( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {\n\t\t\tvec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );\n\t\t\tvec3 pointVector = lPosition.xyz + vViewPosition.xyz;\n\t\t\tfloat pointDistance = 1.0;\n\t\t\tif ( pointLightDistance[ i ] > 0.0 )\n\t\t\t\tpointDistance = 1.0 - min( ( length( pointVector ) / pointLightDistance[ i ] ), 1.0 );\n\t\t\tpointVector = normalize( pointVector );\n\t\t\t#ifdef WRAP_AROUND\n\t\t\t\tfloat pointDiffuseWeightFull = max( dot( normal, pointVector ), 0.0 );\n\t\t\t\tfloat pointDiffuseWeightHalf = max( 0.5 * dot( normal, pointVector ) + 0.5, 0.0 );\n\t\t\t\tvec3 pointDiffuseWeight = mix( vec3( pointDiffuseWeightFull ), vec3( pointDiffuseWeightHalf ), wrapRGB );\n\t\t\t#else\n\t\t\t\tfloat pointDiffuseWeight = max( dot( normal, pointVector ), 0.0 );\n\t\t\t#endif\n\t\t\tpointDiffuse += pointDistance * pointLightColor[ i ] * diffuse * pointDiffuseWeight;\n\t\t\tvec3 pointHalfVector = normalize( pointVector + viewPosition );\n\t\t\tfloat pointDotNormalHalf = max( dot( normal, pointHalfVector ), 0.0 );\n\t\t\tfloat pointSpecularWeight = specularTex.r * max( pow( pointDotNormalHalf, shininess ), 0.0 );\n\t\t\tfloat specularNormalization = ( shininess + 2.0 ) / 8.0;\n\t\t\tvec3 schlick = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( pointVector, pointHalfVector ), 0.0 ), 5.0 );\n\t\t\tpointSpecular += schlick * pointLightColor[ i ] * pointSpecularWeight * pointDiffuseWeight * pointDistance * specularNormalization;\n\t\t}\n\t#endif\n\t#if MAX_SPOT_LIGHTS > 0\n\t\tvec3 spotDiffuse = vec3( 0.0 );\n\t\tvec3 spotSpecular = vec3( 0.0 );\n\t\tfor ( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {\n\t\t\tvec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );\n\t\t\tvec3 spotVector = lPosition.xyz + vViewPosition.xyz;\n\t\t\tfloat spotDistance = 1.0;\n\t\t\tif ( spotLightDistance[ i ] > 0.0 )\n\t\t\t\tspotDistance = 1.0 - min( ( length( spotVector ) / spotLightDistance[ i ] ), 1.0 );\n\t\t\tspotVector = normalize( spotVector );\n\t\t\tfloat spotEffect = dot( spotLightDirection[ i ], normalize( spotLightPosition[ i ] - vWorldPosition ) );\n\t\t\tif ( spotEffect > spotLightAngleCos[ i ] ) {\n\t\t\t\tspotEffect = max( pow( max( spotEffect, 0.0 ), spotLightExponent[ i ] ), 0.0 );\n\t\t\t\t#ifdef WRAP_AROUND\n\t\t\t\t\tfloat spotDiffuseWeightFull = max( dot( normal, spotVector ), 0.0 );\n\t\t\t\t\tfloat spotDiffuseWeightHalf = max( 0.5 * dot( normal, spotVector ) + 0.5, 0.0 );\n\t\t\t\t\tvec3 spotDiffuseWeight = mix( vec3( spotDiffuseWeightFull ), vec3( spotDiffuseWeightHalf ), wrapRGB );\n\t\t\t\t#else\n\t\t\t\t\tfloat spotDiffuseWeight = max( dot( normal, spotVector ), 0.0 );\n\t\t\t\t#endif\n\t\t\t\tspotDiffuse += spotDistance * spotLightColor[ i ] * diffuse * spotDiffuseWeight * spotEffect;\n\t\t\t\tvec3 spotHalfVector = normalize( spotVector + viewPosition );\n\t\t\t\tfloat spotDotNormalHalf = max( dot( normal, spotHalfVector ), 0.0 );\n\t\t\t\tfloat spotSpecularWeight = specularTex.r * max( pow( spotDotNormalHalf, shininess ), 0.0 );\n\t\t\t\tfloat specularNormalization = ( shininess + 2.0 ) / 8.0;\n\t\t\t\tvec3 schlick = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( spotVector, spotHalfVector ), 0.0 ), 5.0 );\n\t\t\t\tspotSpecular += schlick * spotLightColor[ i ] * spotSpecularWeight * spotDiffuseWeight * spotDistance * specularNormalization * spotEffect;\n\t\t\t}\n\t\t}\n\t#endif\n\t#if MAX_DIR_LIGHTS > 0\n\t\tvec3 dirDiffuse = vec3( 0.0 );\n\t\tvec3 dirSpecular = vec3( 0.0 );\n\t\tfor( int i = 0; i < MAX_DIR_LIGHTS; i++ ) {\n\t\t\tvec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );\n\t\t\tvec3 dirVector = normalize( lDirection.xyz );\n\t\t\t#ifdef WRAP_AROUND\n\t\t\t\tfloat directionalLightWeightingFull = max( dot( normal, dirVector ), 0.0 );\n\t\t\t\tfloat directionalLightWeightingHalf = max( 0.5 * dot( normal, dirVector ) + 0.5, 0.0 );\n\t\t\t\tvec3 dirDiffuseWeight = mix( vec3( directionalLightWeightingFull ), vec3( directionalLightWeightingHalf ), wrapRGB );\n\t\t\t#else\n\t\t\t\tfloat dirDiffuseWeight = max( dot( normal, dirVector ), 0.0 );\n\t\t\t#endif\n\t\t\tdirDiffuse += directionalLightColor[ i ] * diffuse * dirDiffuseWeight;\n\t\t\tvec3 dirHalfVector = normalize( dirVector + viewPosition );\n\t\t\tfloat dirDotNormalHalf = max( dot( normal, dirHalfVector ), 0.0 );\n\t\t\tfloat dirSpecularWeight = specularTex.r * max( pow( dirDotNormalHalf, shininess ), 0.0 );\n\t\t\tfloat specularNormalization = ( shininess + 2.0 ) / 8.0;\n\t\t\tvec3 schlick = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( dirVector, dirHalfVector ), 0.0 ), 5.0 );\n\t\t\tdirSpecular += schlick * directionalLightColor[ i ] * dirSpecularWeight * dirDiffuseWeight * specularNormalization;\n\t\t}\n\t#endif\n\t#if MAX_HEMI_LIGHTS > 0\n\t\tvec3 hemiDiffuse = vec3( 0.0 );\n\t\tvec3 hemiSpecular = vec3( 0.0 );\n\t\tfor( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {\n\t\t\tvec4 lDirection = viewMatrix * vec4( hemisphereLightDirection[ i ], 0.0 );\n\t\t\tvec3 lVector = normalize( lDirection.xyz );\n\t\t\tfloat dotProduct = dot( normal, lVector );\n\t\t\tfloat hemiDiffuseWeight = 0.5 * dotProduct + 0.5;\n\t\t\tvec3 hemiColor = mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );\n\t\t\themiDiffuse += diffuse * hemiColor;\n\t\t\tvec3 hemiHalfVectorSky = normalize( lVector + viewPosition );\n\t\t\tfloat hemiDotNormalHalfSky = 0.5 * dot( normal, hemiHalfVectorSky ) + 0.5;\n\t\t\tfloat hemiSpecularWeightSky = specularTex.r * max( pow( max( hemiDotNormalHalfSky, 0.0 ), shininess ), 0.0 );\n\t\t\tvec3 lVectorGround = -lVector;\n\t\t\tvec3 hemiHalfVectorGround = normalize( lVectorGround + viewPosition );\n\t\t\tfloat hemiDotNormalHalfGround = 0.5 * dot( normal, hemiHalfVectorGround ) + 0.5;\n\t\t\tfloat hemiSpecularWeightGround = specularTex.r * max( pow( max( hemiDotNormalHalfGround, 0.0 ), shininess ), 0.0 );\n\t\t\tfloat dotProductGround = dot( normal, lVectorGround );\n\t\t\tfloat specularNormalization = ( shininess + 2.0 ) / 8.0;\n\t\t\tvec3 schlickSky = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( lVector, hemiHalfVectorSky ), 0.0 ), 5.0 );\n\t\t\tvec3 schlickGround = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( lVectorGround, hemiHalfVectorGround ), 0.0 ), 5.0 );\n\t\t\themiSpecular += hemiColor * specularNormalization * ( schlickSky * hemiSpecularWeightSky * max( dotProduct, 0.0 ) + schlickGround * hemiSpecularWeightGround * max( dotProductGround, 0.0 ) );\n\t\t}\n\t#endif\n\tvec3 totalDiffuse = vec3( 0.0 );\n\tvec3 totalSpecular = vec3( 0.0 );\n\t#if MAX_DIR_LIGHTS > 0\n\t\ttotalDiffuse += dirDiffuse;\n\t\ttotalSpecular += dirSpecular;\n\t#endif\n\t#if MAX_HEMI_LIGHTS > 0\n\t\ttotalDiffuse += hemiDiffuse;\n\t\ttotalSpecular += hemiSpecular;\n\t#endif\n\t#if MAX_POINT_LIGHTS > 0\n\t\ttotalDiffuse += pointDiffuse;\n\t\ttotalSpecular += pointSpecular;\n\t#endif\n\t#if MAX_SPOT_LIGHTS > 0\n\t\ttotalDiffuse += spotDiffuse;\n\t\ttotalSpecular += spotSpecular;\n\t#endif\n\t#ifdef METAL\n\t\tgl_FragColor.xyz = gl_FragColor.xyz * ( totalDiffuse + ambientLightColor * ambient + totalSpecular );\n\t#else\n\t\tgl_FragColor.xyz = gl_FragColor.xyz * ( totalDiffuse + ambientLightColor * ambient ) + totalSpecular;\n\t#endif\n\tif ( enableReflection ) {\n\t\tvec3 vReflect;\n\t\tvec3 cameraToVertex = normalize( vWorldPosition - cameraPosition );\n\t\tif ( useRefract ) {\n\t\t\tvReflect = refract( cameraToVertex, normal, refractionRatio );\n\t\t} else {\n\t\t\tvReflect = reflect( cameraToVertex, normal );\n\t\t}\n\t\tvec4 cubeColor = textureCube( tCube, vec3( -vReflect.x, vReflect.yz ) );\n\t\t#ifdef GAMMA_INPUT\n\t\t\tcubeColor.xyz *= cubeColor.xyz;\n\t\t#endif\n\t\tgl_FragColor.xyz = mix( gl_FragColor.xyz, cubeColor.xyz, specularTex.r * reflectivity );\n\t}", +THREE.ShaderChunk.shadowmap_fragment,THREE.ShaderChunk.linear_to_gamma_fragment,THREE.ShaderChunk.fog_fragment,"}"].join("\n"),vertexShader:["attribute vec4 tangent;\nuniform vec2 uOffset;\nuniform vec2 uRepeat;\nuniform bool enableDisplacement;\n#ifdef VERTEX_TEXTURES\n\tuniform sampler2D tDisplacement;\n\tuniform float uDisplacementScale;\n\tuniform float uDisplacementBias;\n#endif\nvarying vec3 vTangent;\nvarying vec3 vBinormal;\nvarying vec3 vNormal;\nvarying vec2 vUv;\nvarying vec3 vWorldPosition;\nvarying vec3 vViewPosition;", +THREE.ShaderChunk.skinning_pars_vertex,THREE.ShaderChunk.shadowmap_pars_vertex,THREE.ShaderChunk.logdepthbuf_pars_vertex,"void main() {",THREE.ShaderChunk.skinbase_vertex,THREE.ShaderChunk.skinnormal_vertex,"\t#ifdef USE_SKINNING\n\t\tvNormal = normalize( normalMatrix * skinnedNormal.xyz );\n\t\tvec4 skinnedTangent = skinMatrix * vec4( tangent.xyz, 0.0 );\n\t\tvTangent = normalize( normalMatrix * skinnedTangent.xyz );\n\t#else\n\t\tvNormal = normalize( normalMatrix * normal );\n\t\tvTangent = normalize( normalMatrix * tangent.xyz );\n\t#endif\n\tvBinormal = normalize( cross( vNormal, vTangent ) * tangent.w );\n\tvUv = uv * uRepeat + uOffset;\n\tvec3 displacedPosition;\n\t#ifdef VERTEX_TEXTURES\n\t\tif ( enableDisplacement ) {\n\t\t\tvec3 dv = texture2D( tDisplacement, uv ).xyz;\n\t\t\tfloat df = uDisplacementScale * dv.x + uDisplacementBias;\n\t\t\tdisplacedPosition = position + normalize( normal ) * df;\n\t\t} else {\n\t\t\t#ifdef USE_SKINNING\n\t\t\t\tvec4 skinVertex = bindMatrix * vec4( position, 1.0 );\n\t\t\t\tvec4 skinned = vec4( 0.0 );\n\t\t\t\tskinned += boneMatX * skinVertex * skinWeight.x;\n\t\t\t\tskinned += boneMatY * skinVertex * skinWeight.y;\n\t\t\t\tskinned += boneMatZ * skinVertex * skinWeight.z;\n\t\t\t\tskinned += boneMatW * skinVertex * skinWeight.w;\n\t\t\t\tskinned = bindMatrixInverse * skinned;\n\t\t\t\tdisplacedPosition = skinned.xyz;\n\t\t\t#else\n\t\t\t\tdisplacedPosition = position;\n\t\t\t#endif\n\t\t}\n\t#else\n\t\t#ifdef USE_SKINNING\n\t\t\tvec4 skinVertex = bindMatrix * vec4( position, 1.0 );\n\t\t\tvec4 skinned = vec4( 0.0 );\n\t\t\tskinned += boneMatX * skinVertex * skinWeight.x;\n\t\t\tskinned += boneMatY * skinVertex * skinWeight.y;\n\t\t\tskinned += boneMatZ * skinVertex * skinWeight.z;\n\t\t\tskinned += boneMatW * skinVertex * skinWeight.w;\n\t\t\tskinned = bindMatrixInverse * skinned;\n\t\t\tdisplacedPosition = skinned.xyz;\n\t\t#else\n\t\t\tdisplacedPosition = position;\n\t\t#endif\n\t#endif\n\tvec4 mvPosition = modelViewMatrix * vec4( displacedPosition, 1.0 );\n\tvec4 worldPosition = modelMatrix * vec4( displacedPosition, 1.0 );\n\tgl_Position = projectionMatrix * mvPosition;", +THREE.ShaderChunk.logdepthbuf_vertex,"\tvWorldPosition = worldPosition.xyz;\n\tvViewPosition = -mvPosition.xyz;\n\t#ifdef USE_SHADOWMAP\n\t\tfor( int i = 0; i < MAX_SHADOWS; i ++ ) {\n\t\t\tvShadowCoord[ i ] = shadowMatrix[ i ] * worldPosition;\n\t\t}\n\t#endif\n}"].join("\n")},cube:{uniforms:{tCube:{type:"t",value:null},tFlip:{type:"f",value:-1}},vertexShader:["varying vec3 vWorldPosition;",THREE.ShaderChunk.logdepthbuf_pars_vertex,"void main() {\n\tvec4 worldPosition = modelMatrix * vec4( position, 1.0 );\n\tvWorldPosition = worldPosition.xyz;\n\tgl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", +THREE.ShaderChunk.logdepthbuf_vertex,"}"].join("\n"),fragmentShader:["uniform samplerCube tCube;\nuniform float tFlip;\nvarying vec3 vWorldPosition;",THREE.ShaderChunk.logdepthbuf_pars_fragment,"void main() {\n\tgl_FragColor = textureCube( tCube, vec3( tFlip * vWorldPosition.x, vWorldPosition.yz ) );",THREE.ShaderChunk.logdepthbuf_fragment,"}"].join("\n")},depthRGBA:{uniforms:{},vertexShader:[THREE.ShaderChunk.morphtarget_pars_vertex,THREE.ShaderChunk.skinning_pars_vertex,THREE.ShaderChunk.logdepthbuf_pars_vertex, +"void main() {",THREE.ShaderChunk.skinbase_vertex,THREE.ShaderChunk.morphtarget_vertex,THREE.ShaderChunk.skinning_vertex,THREE.ShaderChunk.default_vertex,THREE.ShaderChunk.logdepthbuf_vertex,"}"].join("\n"),fragmentShader:[THREE.ShaderChunk.logdepthbuf_pars_fragment,"vec4 pack_depth( const in float depth ) {\n\tconst vec4 bit_shift = vec4( 256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0 );\n\tconst vec4 bit_mask = vec4( 0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0 );\n\tvec4 res = mod( depth * bit_shift * vec4( 255 ), vec4( 256 ) ) / vec4( 255 );\n\tres -= res.xxyz * bit_mask;\n\treturn res;\n}\nvoid main() {", +THREE.ShaderChunk.logdepthbuf_fragment,"\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tgl_FragData[ 0 ] = pack_depth( gl_FragDepthEXT );\n\t#else\n\t\tgl_FragData[ 0 ] = pack_depth( gl_FragCoord.z );\n\t#endif\n}"].join("\n")}}; +THREE.WebGLRenderer=function(a){function b(a){var b=a.geometry;a=a.material;var c=b.vertices.length;if(a.attributes){void 0===b.__webglCustomAttributesList&&(b.__webglCustomAttributesList=[]);for(var d in a.attributes){var e=a.attributes[d];if(!e.__webglInitialized||e.createUniqueBuffers){e.__webglInitialized=!0;var f=1;"v2"===e.type?f=2:"v3"===e.type?f=3:"v4"===e.type?f=4:"c"===e.type&&(f=3);e.size=f;e.array=new Float32Array(c*f);e.buffer=l.createBuffer();e.buffer.belongsToAttribute=d;e.needsUpdate= +!0}b.__webglCustomAttributesList.push(e)}}}function c(a,b){var c=b.geometry,e=a.faces3,f=3*e.length,g=1*e.length,h=3*e.length,e=d(b,a);a.__vertexArray=new Float32Array(3*f);a.__normalArray=new Float32Array(3*f);a.__colorArray=new Float32Array(3*f);a.__uvArray=new Float32Array(2*f);1Aa;Aa++)Cb=ma[Aa],Ta[Sa]=Cb.x,Ta[Sa+1]=Cb.y,Ta[Sa+2]=Cb.z,Sa+=3;else for(Aa=0;3>Aa;Aa++)Ta[Sa]=pa.x,Ta[Sa+1]=pa.y,Ta[Sa+2]=pa.z,Sa+=3;l.bindBuffer(l.ARRAY_BUFFER,C.__webglNormalBuffer);l.bufferData(l.ARRAY_BUFFER, +Ta,S)}if(Kc&&ua){M=0;for(ea=N.length;MAa;Aa++)Oa=hb[Aa],sb[qb]=Oa.x,sb[qb+1]=Oa.y,qb+=2;0Aa;Aa++)Qb=za[Aa],fb[rb]=Qb.x,fb[rb+1]=Qb.y,rb+=2;0h&&(f[v].counter+=1,k=f[v].hash+"_"+f[v].counter,k in r||(p={id:rc++, +faces3:[],materialIndex:v,vertices:0,numMorphTargets:m,numMorphNormals:n},r[k]=p,q.push(p)));r[k].faces3.push(t);r[k].vertices+=3}a[g]=q;d.groupsNeedUpdate=!1}a=xb[d.id];g=0;for(e=a.length;gDa;Da++)kb[Da]=!J.autoScaleCubemaps||Ob||Tb?Tb?ua.image[Da].image:ua.image[Da]:R(ua.image[Da],$c);var ka=kb[0],Zb=THREE.Math.isPowerOfTwo(ka.width)&&THREE.Math.isPowerOfTwo(ka.height),ab=Q(ua.format),Fb=Q(ua.type);F(l.TEXTURE_CUBE_MAP,ua,Zb);for(Da=0;6>Da;Da++)if(Ob)for(var gb,$b=kb[Da].mipmaps,ga=0,Xb=$b.length;ga=Oc&&console.warn("WebGLRenderer: trying to use "+a+" texture units while this GPU supports only "+ +Oc);dc+=1;return a}function x(a,b){a._modelViewMatrix.multiplyMatrices(b.matrixWorldInverse,a.matrixWorld);a._normalMatrix.getNormalMatrix(a._modelViewMatrix)}function D(a,b,c,d){a[b]=c.r*c.r*d;a[b+1]=c.g*c.g*d;a[b+2]=c.b*c.b*d}function E(a,b,c,d){a[b]=c.r*d;a[b+1]=c.g*d;a[b+2]=c.b*d}function A(a){a!==Pc&&(l.lineWidth(a),Pc=a)}function B(a,b,c){Qc!==a&&(a?l.enable(l.POLYGON_OFFSET_FILL):l.disable(l.POLYGON_OFFSET_FILL),Qc=a);!a||Rc===b&&Sc===c||(l.polygonOffset(b,c),Rc=b,Sc=c)}function F(a,b,c){c? +(l.texParameteri(a,l.TEXTURE_WRAP_S,Q(b.wrapS)),l.texParameteri(a,l.TEXTURE_WRAP_T,Q(b.wrapT)),l.texParameteri(a,l.TEXTURE_MAG_FILTER,Q(b.magFilter)),l.texParameteri(a,l.TEXTURE_MIN_FILTER,Q(b.minFilter))):(l.texParameteri(a,l.TEXTURE_WRAP_S,l.CLAMP_TO_EDGE),l.texParameteri(a,l.TEXTURE_WRAP_T,l.CLAMP_TO_EDGE),l.texParameteri(a,l.TEXTURE_MAG_FILTER,T(b.magFilter)),l.texParameteri(a,l.TEXTURE_MIN_FILTER,T(b.minFilter)));(c=pa.get("EXT_texture_filter_anisotropic"))&&b.type!==THREE.FloatType&&(1b||a.height>b){var c=b/Math.max(a.width,a.height),d=document.createElement("canvas");d.width=Math.floor(a.width*c);d.height=Math.floor(a.height*c);d.getContext("2d").drawImage(a,0,0,a.width,a.height,0,0,d.width,d.height);console.log("THREE.WebGLRenderer:",a,"is too big ("+a.width+"x"+a.height+"). Resized to "+d.width+"x"+d.height+ +".");return d}return a}function H(a,b){l.bindRenderbuffer(l.RENDERBUFFER,a);b.depthBuffer&&!b.stencilBuffer?(l.renderbufferStorage(l.RENDERBUFFER,l.DEPTH_COMPONENT16,b.width,b.height),l.framebufferRenderbuffer(l.FRAMEBUFFER,l.DEPTH_ATTACHMENT,l.RENDERBUFFER,a)):b.depthBuffer&&b.stencilBuffer?(l.renderbufferStorage(l.RENDERBUFFER,l.DEPTH_STENCIL,b.width,b.height),l.framebufferRenderbuffer(l.FRAMEBUFFER,l.DEPTH_STENCIL_ATTACHMENT,l.RENDERBUFFER,a)):l.renderbufferStorage(l.RENDERBUFFER,l.RGBA4,b.width, +b.height)}function C(a){a instanceof THREE.WebGLRenderTargetCube?(l.bindTexture(l.TEXTURE_CUBE_MAP,a.__webglTexture),l.generateMipmap(l.TEXTURE_CUBE_MAP),l.bindTexture(l.TEXTURE_CUBE_MAP,null)):(l.bindTexture(l.TEXTURE_2D,a.__webglTexture),l.generateMipmap(l.TEXTURE_2D),l.bindTexture(l.TEXTURE_2D,null))}function T(a){return a===THREE.NearestFilter||a===THREE.NearestMipMapNearestFilter||a===THREE.NearestMipMapLinearFilter?l.NEAREST:l.LINEAR}function Q(a){var b;if(a===THREE.RepeatWrapping)return l.REPEAT; +if(a===THREE.ClampToEdgeWrapping)return l.CLAMP_TO_EDGE;if(a===THREE.MirroredRepeatWrapping)return l.MIRRORED_REPEAT;if(a===THREE.NearestFilter)return l.NEAREST;if(a===THREE.NearestMipMapNearestFilter)return l.NEAREST_MIPMAP_NEAREST;if(a===THREE.NearestMipMapLinearFilter)return l.NEAREST_MIPMAP_LINEAR;if(a===THREE.LinearFilter)return l.LINEAR;if(a===THREE.LinearMipMapNearestFilter)return l.LINEAR_MIPMAP_NEAREST;if(a===THREE.LinearMipMapLinearFilter)return l.LINEAR_MIPMAP_LINEAR;if(a===THREE.UnsignedByteType)return l.UNSIGNED_BYTE; +if(a===THREE.UnsignedShort4444Type)return l.UNSIGNED_SHORT_4_4_4_4;if(a===THREE.UnsignedShort5551Type)return l.UNSIGNED_SHORT_5_5_5_1;if(a===THREE.UnsignedShort565Type)return l.UNSIGNED_SHORT_5_6_5;if(a===THREE.ByteType)return l.BYTE;if(a===THREE.ShortType)return l.SHORT;if(a===THREE.UnsignedShortType)return l.UNSIGNED_SHORT;if(a===THREE.IntType)return l.INT;if(a===THREE.UnsignedIntType)return l.UNSIGNED_INT;if(a===THREE.FloatType)return l.FLOAT;if(a===THREE.AlphaFormat)return l.ALPHA;if(a===THREE.RGBFormat)return l.RGB; +if(a===THREE.RGBAFormat)return l.RGBA;if(a===THREE.LuminanceFormat)return l.LUMINANCE;if(a===THREE.LuminanceAlphaFormat)return l.LUMINANCE_ALPHA;if(a===THREE.AddEquation)return l.FUNC_ADD;if(a===THREE.SubtractEquation)return l.FUNC_SUBTRACT;if(a===THREE.ReverseSubtractEquation)return l.FUNC_REVERSE_SUBTRACT;if(a===THREE.ZeroFactor)return l.ZERO;if(a===THREE.OneFactor)return l.ONE;if(a===THREE.SrcColorFactor)return l.SRC_COLOR;if(a===THREE.OneMinusSrcColorFactor)return l.ONE_MINUS_SRC_COLOR;if(a=== +THREE.SrcAlphaFactor)return l.SRC_ALPHA;if(a===THREE.OneMinusSrcAlphaFactor)return l.ONE_MINUS_SRC_ALPHA;if(a===THREE.DstAlphaFactor)return l.DST_ALPHA;if(a===THREE.OneMinusDstAlphaFactor)return l.ONE_MINUS_DST_ALPHA;if(a===THREE.DstColorFactor)return l.DST_COLOR;if(a===THREE.OneMinusDstColorFactor)return l.ONE_MINUS_DST_COLOR;if(a===THREE.SrcAlphaSaturateFactor)return l.SRC_ALPHA_SATURATE;b=pa.get("WEBGL_compressed_texture_s3tc");if(null!==b){if(a===THREE.RGB_S3TC_DXT1_Format)return b.COMPRESSED_RGB_S3TC_DXT1_EXT; +if(a===THREE.RGBA_S3TC_DXT1_Format)return b.COMPRESSED_RGBA_S3TC_DXT1_EXT;if(a===THREE.RGBA_S3TC_DXT3_Format)return b.COMPRESSED_RGBA_S3TC_DXT3_EXT;if(a===THREE.RGBA_S3TC_DXT5_Format)return b.COMPRESSED_RGBA_S3TC_DXT5_EXT}b=pa.get("WEBGL_compressed_texture_pvrtc");if(null!==b){if(a===THREE.RGB_PVRTC_4BPPV1_Format)return b.COMPRESSED_RGB_PVRTC_4BPPV1_IMG;if(a===THREE.RGB_PVRTC_2BPPV1_Format)return b.COMPRESSED_RGB_PVRTC_2BPPV1_IMG;if(a===THREE.RGBA_PVRTC_4BPPV1_Format)return b.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; +if(a===THREE.RGBA_PVRTC_2BPPV1_Format)return b.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG}b=pa.get("EXT_blend_minmax");if(null!==b){if(a===THREE.MinEquation)return b.MIN_EXT;if(a===THREE.MaxEquation)return b.MAX_EXT}return 0}console.log("THREE.WebGLRenderer",THREE.REVISION);a=a||{};var O=void 0!==a.canvas?a.canvas:document.createElement("canvas"),S=void 0!==a.context?a.context:null,X=void 0!==a.precision?a.precision:"highp",Y=void 0!==a.alpha?a.alpha:!1,la=void 0!==a.depth?a.depth:!0,ma=void 0!==a.stencil? +a.stencil:!0,ya=void 0!==a.antialias?a.antialias:!1,P=void 0!==a.premultipliedAlpha?a.premultipliedAlpha:!0,Ga=void 0!==a.preserveDrawingBuffer?a.preserveDrawingBuffer:!1,Fa=void 0!==a.logarithmicDepthBuffer?a.logarithmicDepthBuffer:!1,za=new THREE.Color(0),bb=0,cb=[],ob={},jb=[],Jb=[],Ib=[],yb=[],Ra=[];this.domElement=O;this.context=null;this.devicePixelRatio=void 0!==a.devicePixelRatio?a.devicePixelRatio:void 0!==self.devicePixelRatio?self.devicePixelRatio:1;this.sortObjects=this.autoClearStencil= +this.autoClearDepth=this.autoClearColor=this.autoClear=!0;this.shadowMapEnabled=this.gammaOutput=this.gammaInput=!1;this.shadowMapType=THREE.PCFShadowMap;this.shadowMapCullFace=THREE.CullFaceFront;this.shadowMapCascade=this.shadowMapDebug=!1;this.maxMorphTargets=8;this.maxMorphNormals=4;this.autoScaleCubemaps=!0;this.info={memory:{programs:0,geometries:0,textures:0},render:{calls:0,vertices:0,faces:0,points:0}};var J=this,hb=[],tc=null,Tc=null,Kb=-1,Oa=-1,ec=null,dc=0,Lb=-1,Mb=-1,pb=-1,Nb=-1,Ob=-1, +Xb=-1,Yb=-1,nb=-1,Qc=null,Rc=null,Sc=null,Pc=null,Pb=0,kc=0,lc=O.width,mc=O.height,Uc=0,Vc=0,wb=new Uint8Array(16),ib=new Uint8Array(16),Ec=new THREE.Frustum,Ac=new THREE.Matrix4,Gc=new THREE.Matrix4,Na=new THREE.Vector3,sa=new THREE.Vector3,fc=!0,Mc={ambient:[0,0,0],directional:{length:0,colors:[],positions:[]},point:{length:0,colors:[],positions:[],distances:[]},spot:{length:0,colors:[],positions:[],distances:[],directions:[],anglesCos:[],exponents:[]},hemi:{length:0,skyColors:[],groundColors:[], +positions:[]}},l;try{var Wc={alpha:Y,depth:la,stencil:ma,antialias:ya,premultipliedAlpha:P,preserveDrawingBuffer:Ga};l=S||O.getContext("webgl",Wc)||O.getContext("experimental-webgl",Wc);if(null===l){if(null!==O.getContext("webgl"))throw"Error creating WebGL context with your selected attributes.";throw"Error creating WebGL context.";}}catch(ad){console.error(ad)}void 0===l.getShaderPrecisionFormat&&(l.getShaderPrecisionFormat=function(){return{rangeMin:1,rangeMax:1,precision:1}});var pa=new THREE.WebGLExtensions(l); +pa.get("OES_texture_float");pa.get("OES_texture_float_linear");pa.get("OES_standard_derivatives");Fa&&pa.get("EXT_frag_depth");l.clearColor(0,0,0,1);l.clearDepth(1);l.clearStencil(0);l.enable(l.DEPTH_TEST);l.depthFunc(l.LEQUAL);l.frontFace(l.CCW);l.cullFace(l.BACK);l.enable(l.CULL_FACE);l.enable(l.BLEND);l.blendEquation(l.FUNC_ADD);l.blendFunc(l.SRC_ALPHA,l.ONE_MINUS_SRC_ALPHA);l.viewport(Pb,kc,lc,mc);l.clearColor(za.r,za.g,za.b,bb);this.context=l;var Oc=l.getParameter(l.MAX_TEXTURE_IMAGE_UNITS), +bd=l.getParameter(l.MAX_VERTEX_TEXTURE_IMAGE_UNITS),cd=l.getParameter(l.MAX_TEXTURE_SIZE),$c=l.getParameter(l.MAX_CUBE_MAP_TEXTURE_SIZE),sc=0b;b++)l.deleteFramebuffer(a.__webglFramebuffer[b]),l.deleteRenderbuffer(a.__webglRenderbuffer[b]); +else l.deleteFramebuffer(a.__webglFramebuffer),l.deleteRenderbuffer(a.__webglRenderbuffer);delete a.__webglFramebuffer;delete a.__webglRenderbuffer}J.info.memory.textures--},Dc=function(a){a=a.target;a.removeEventListener("dispose",Dc);Cc(a)},Yc=function(a){for(var b="__webglVertexBuffer __webglNormalBuffer __webglTangentBuffer __webglColorBuffer __webglUVBuffer __webglUV2Buffer __webglSkinIndicesBuffer __webglSkinWeightsBuffer __webglFaceBuffer __webglLineBuffer __webglLineDistanceBuffer".split(" "), +c=0,d=b.length;cd.numSupportedMorphTargets?(n.sort(p),n.length=d.numSupportedMorphTargets):n.length>d.numSupportedMorphNormals?n.sort(p):0===n.length&&n.push([0, +0]);for(m=0;mf;f++){a.__webglFramebuffer[f]=l.createFramebuffer();a.__webglRenderbuffer[f]=l.createRenderbuffer();l.texImage2D(l.TEXTURE_CUBE_MAP_POSITIVE_X+f,0,d,a.width,a.height,0,d,e,null);var g=a,h=l.TEXTURE_CUBE_MAP_POSITIVE_X+f;l.bindFramebuffer(l.FRAMEBUFFER,a.__webglFramebuffer[f]);l.framebufferTexture2D(l.FRAMEBUFFER,l.COLOR_ATTACHMENT0,h,g.__webglTexture,0);H(a.__webglRenderbuffer[f],a)}c&&l.generateMipmap(l.TEXTURE_CUBE_MAP)}else a.__webglFramebuffer= +l.createFramebuffer(),a.__webglRenderbuffer=a.shareDepthFrom?a.shareDepthFrom.__webglRenderbuffer:l.createRenderbuffer(),l.bindTexture(l.TEXTURE_2D,a.__webglTexture),F(l.TEXTURE_2D,a,c),l.texImage2D(l.TEXTURE_2D,0,d,a.width,a.height,0,d,e,null),d=l.TEXTURE_2D,l.bindFramebuffer(l.FRAMEBUFFER,a.__webglFramebuffer),l.framebufferTexture2D(l.FRAMEBUFFER,l.COLOR_ATTACHMENT0,d,a.__webglTexture,0),a.shareDepthFrom?a.depthBuffer&&!a.stencilBuffer?l.framebufferRenderbuffer(l.FRAMEBUFFER,l.DEPTH_ATTACHMENT, +l.RENDERBUFFER,a.__webglRenderbuffer):a.depthBuffer&&a.stencilBuffer&&l.framebufferRenderbuffer(l.FRAMEBUFFER,l.DEPTH_STENCIL_ATTACHMENT,l.RENDERBUFFER,a.__webglRenderbuffer):H(a.__webglRenderbuffer,a),c&&l.generateMipmap(l.TEXTURE_2D);b?l.bindTexture(l.TEXTURE_CUBE_MAP,null):l.bindTexture(l.TEXTURE_2D,null);l.bindRenderbuffer(l.RENDERBUFFER,null);l.bindFramebuffer(l.FRAMEBUFFER,null)}a?(b=b?a.__webglFramebuffer[a.activeCubeFace]:a.__webglFramebuffer,c=a.width,a=a.height,e=d=0):(b=null,c=lc,a=mc, +d=Pb,e=kc);b!==Tc&&(l.bindFramebuffer(l.FRAMEBUFFER,b),l.viewport(d,e,c,a),Tc=b);Uc=c;Vc=a};this.initMaterial=function(){console.warn("THREE.WebGLRenderer: .initMaterial() has been removed.")};this.addPrePlugin=function(){console.warn("THREE.WebGLRenderer: .addPrePlugin() has been removed.")};this.addPostPlugin=function(){console.warn("THREE.WebGLRenderer: .addPostPlugin() has been removed.")};this.updateShadowMap=function(){console.warn("THREE.WebGLRenderer: .updateShadowMap() has been removed.")}}; +THREE.WebGLRenderTarget=function(a,b,c){this.width=a;this.height=b;c=c||{};this.wrapS=void 0!==c.wrapS?c.wrapS:THREE.ClampToEdgeWrapping;this.wrapT=void 0!==c.wrapT?c.wrapT:THREE.ClampToEdgeWrapping;this.magFilter=void 0!==c.magFilter?c.magFilter:THREE.LinearFilter;this.minFilter=void 0!==c.minFilter?c.minFilter:THREE.LinearMipMapLinearFilter;this.anisotropy=void 0!==c.anisotropy?c.anisotropy:1;this.offset=new THREE.Vector2(0,0);this.repeat=new THREE.Vector2(1,1);this.format=void 0!==c.format?c.format: +THREE.RGBAFormat;this.type=void 0!==c.type?c.type:THREE.UnsignedByteType;this.depthBuffer=void 0!==c.depthBuffer?c.depthBuffer:!0;this.stencilBuffer=void 0!==c.stencilBuffer?c.stencilBuffer:!0;this.generateMipmaps=!0;this.shareDepthFrom=null}; +THREE.WebGLRenderTarget.prototype={constructor:THREE.WebGLRenderTarget,setSize:function(a,b){this.width=a;this.height=b},clone:function(){var a=new THREE.WebGLRenderTarget(this.width,this.height);a.wrapS=this.wrapS;a.wrapT=this.wrapT;a.magFilter=this.magFilter;a.minFilter=this.minFilter;a.anisotropy=this.anisotropy;a.offset.copy(this.offset);a.repeat.copy(this.repeat);a.format=this.format;a.type=this.type;a.depthBuffer=this.depthBuffer;a.stencilBuffer=this.stencilBuffer;a.generateMipmaps=this.generateMipmaps; +a.shareDepthFrom=this.shareDepthFrom;return a},dispose:function(){this.dispatchEvent({type:"dispose"})}};THREE.EventDispatcher.prototype.apply(THREE.WebGLRenderTarget.prototype);THREE.WebGLRenderTargetCube=function(a,b,c){THREE.WebGLRenderTarget.call(this,a,b,c);this.activeCubeFace=0};THREE.WebGLRenderTargetCube.prototype=Object.create(THREE.WebGLRenderTarget.prototype); +THREE.WebGLExtensions=function(a){var b={};this.get=function(c){if(void 0!==b[c])return b[c];var d;switch(c){case "OES_texture_float":d=a.getExtension("OES_texture_float");break;case "OES_texture_float_linear":d=a.getExtension("OES_texture_float_linear");break;case "OES_standard_derivatives":d=a.getExtension("OES_standard_derivatives");break;case "EXT_texture_filter_anisotropic":d=a.getExtension("EXT_texture_filter_anisotropic")||a.getExtension("MOZ_EXT_texture_filter_anisotropic")||a.getExtension("WEBKIT_EXT_texture_filter_anisotropic"); +break;case "WEBGL_compressed_texture_s3tc":d=a.getExtension("WEBGL_compressed_texture_s3tc")||a.getExtension("MOZ_WEBGL_compressed_texture_s3tc")||a.getExtension("WEBKIT_WEBGL_compressed_texture_s3tc");break;case "WEBGL_compressed_texture_pvrtc":d=a.getExtension("WEBGL_compressed_texture_pvrtc")||a.getExtension("WEBKIT_WEBGL_compressed_texture_pvrtc");break;case "OES_element_index_uint":d=a.getExtension("OES_element_index_uint");break;case "EXT_blend_minmax":d=a.getExtension("EXT_blend_minmax");break; +case "EXT_frag_depth":d=a.getExtension("EXT_frag_depth")}null===d&&console.log("THREE.WebGLRenderer: "+c+" extension not supported.");return b[c]=d}}; +THREE.WebGLProgram=function(){var a=0;return function(b,c,d,e){var f=b.context,g=d.defines,h=d.__webglShader.uniforms,k=d.attributes,n=d.__webglShader.vertexShader,p=d.__webglShader.fragmentShader,q=d.index0AttributeName;void 0===q&&!0===e.morphTargets&&(q="position");var m="SHADOWMAP_TYPE_BASIC";e.shadowMapType===THREE.PCFShadowMap?m="SHADOWMAP_TYPE_PCF":e.shadowMapType===THREE.PCFSoftShadowMap&&(m="SHADOWMAP_TYPE_PCF_SOFT");var r,t;r=[];for(var s in g)t=g[s],!1!==t&&(t="#define "+s+" "+t,r.push(t)); +r=r.join("\n");g=f.createProgram();d instanceof THREE.RawShaderMaterial?b=d="":(d=["precision "+e.precision+" float;","precision "+e.precision+" int;",r,e.supportsVertexTextures?"#define VERTEX_TEXTURES":"",b.gammaInput?"#define GAMMA_INPUT":"",b.gammaOutput?"#define GAMMA_OUTPUT":"","#define MAX_DIR_LIGHTS "+e.maxDirLights,"#define MAX_POINT_LIGHTS "+e.maxPointLights,"#define MAX_SPOT_LIGHTS "+e.maxSpotLights,"#define MAX_HEMI_LIGHTS "+e.maxHemiLights,"#define MAX_SHADOWS "+e.maxShadows,"#define MAX_BONES "+ +e.maxBones,e.map?"#define USE_MAP":"",e.envMap?"#define USE_ENVMAP":"",e.lightMap?"#define USE_LIGHTMAP":"",e.bumpMap?"#define USE_BUMPMAP":"",e.normalMap?"#define USE_NORMALMAP":"",e.specularMap?"#define USE_SPECULARMAP":"",e.alphaMap?"#define USE_ALPHAMAP":"",e.vertexColors?"#define USE_COLOR":"",e.skinning?"#define USE_SKINNING":"",e.useVertexTexture?"#define BONE_TEXTURE":"",e.morphTargets?"#define USE_MORPHTARGETS":"",e.morphNormals?"#define USE_MORPHNORMALS":"",e.wrapAround?"#define WRAP_AROUND": +"",e.doubleSided?"#define DOUBLE_SIDED":"",e.flipSided?"#define FLIP_SIDED":"",e.shadowMapEnabled?"#define USE_SHADOWMAP":"",e.shadowMapEnabled?"#define "+m:"",e.shadowMapDebug?"#define SHADOWMAP_DEBUG":"",e.shadowMapCascade?"#define SHADOWMAP_CASCADE":"",e.sizeAttenuation?"#define USE_SIZEATTENUATION":"",e.logarithmicDepthBuffer?"#define USE_LOGDEPTHBUF":"","uniform mat4 modelMatrix;\nuniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nuniform mat4 viewMatrix;\nuniform mat3 normalMatrix;\nuniform vec3 cameraPosition;\nattribute vec3 position;\nattribute vec3 normal;\nattribute vec2 uv;\nattribute vec2 uv2;\n#ifdef USE_COLOR\n\tattribute vec3 color;\n#endif\n#ifdef USE_MORPHTARGETS\n\tattribute vec3 morphTarget0;\n\tattribute vec3 morphTarget1;\n\tattribute vec3 morphTarget2;\n\tattribute vec3 morphTarget3;\n\t#ifdef USE_MORPHNORMALS\n\t\tattribute vec3 morphNormal0;\n\t\tattribute vec3 morphNormal1;\n\t\tattribute vec3 morphNormal2;\n\t\tattribute vec3 morphNormal3;\n\t#else\n\t\tattribute vec3 morphTarget4;\n\t\tattribute vec3 morphTarget5;\n\t\tattribute vec3 morphTarget6;\n\t\tattribute vec3 morphTarget7;\n\t#endif\n#endif\n#ifdef USE_SKINNING\n\tattribute vec4 skinIndex;\n\tattribute vec4 skinWeight;\n#endif\n"].join("\n"), +b=["precision "+e.precision+" float;","precision "+e.precision+" int;",e.bumpMap||e.normalMap?"#extension GL_OES_standard_derivatives : enable":"",r,"#define MAX_DIR_LIGHTS "+e.maxDirLights,"#define MAX_POINT_LIGHTS "+e.maxPointLights,"#define MAX_SPOT_LIGHTS "+e.maxSpotLights,"#define MAX_HEMI_LIGHTS "+e.maxHemiLights,"#define MAX_SHADOWS "+e.maxShadows,e.alphaTest?"#define ALPHATEST "+e.alphaTest:"",b.gammaInput?"#define GAMMA_INPUT":"",b.gammaOutput?"#define GAMMA_OUTPUT":"",e.useFog&&e.fog?"#define USE_FOG": +"",e.useFog&&e.fogExp?"#define FOG_EXP2":"",e.map?"#define USE_MAP":"",e.envMap?"#define USE_ENVMAP":"",e.lightMap?"#define USE_LIGHTMAP":"",e.bumpMap?"#define USE_BUMPMAP":"",e.normalMap?"#define USE_NORMALMAP":"",e.specularMap?"#define USE_SPECULARMAP":"",e.alphaMap?"#define USE_ALPHAMAP":"",e.vertexColors?"#define USE_COLOR":"",e.metal?"#define METAL":"",e.wrapAround?"#define WRAP_AROUND":"",e.doubleSided?"#define DOUBLE_SIDED":"",e.flipSided?"#define FLIP_SIDED":"",e.shadowMapEnabled?"#define USE_SHADOWMAP": +"",e.shadowMapEnabled?"#define "+m:"",e.shadowMapDebug?"#define SHADOWMAP_DEBUG":"",e.shadowMapCascade?"#define SHADOWMAP_CASCADE":"",e.logarithmicDepthBuffer?"#define USE_LOGDEPTHBUF":"","uniform mat4 viewMatrix;\nuniform vec3 cameraPosition;\n"].join("\n"));n=new THREE.WebGLShader(f,f.VERTEX_SHADER,d+n);p=new THREE.WebGLShader(f,f.FRAGMENT_SHADER,b+p);f.attachShader(g,n);f.attachShader(g,p);void 0!==q&&f.bindAttribLocation(g,0,q);f.linkProgram(g);!1===f.getProgramParameter(g,f.LINK_STATUS)&&(console.error("THREE.WebGLProgram: Could not initialise shader."), +console.error("gl.VALIDATE_STATUS",f.getProgramParameter(g,f.VALIDATE_STATUS)),console.error("gl.getError()",f.getError()));""!==f.getProgramInfoLog(g)&&console.warn("THREE.WebGLProgram: gl.getProgramInfoLog()",f.getProgramInfoLog(g));f.deleteShader(n);f.deleteShader(p);q="viewMatrix modelViewMatrix projectionMatrix normalMatrix modelMatrix cameraPosition morphTargetInfluences bindMatrix bindMatrixInverse".split(" ");e.useVertexTexture?(q.push("boneTexture"),q.push("boneTextureWidth"),q.push("boneTextureHeight")): +q.push("boneGlobalMatrices");e.logarithmicDepthBuffer&&q.push("logDepthBufFC");for(var u in h)q.push(u);h=q;u={};q=0;for(b=h.length;qT;T++)F[T]=new THREE.Vector3,A[T]=new THREE.Vector3;F=B.shadowCascadeNearZ[C];B=B.shadowCascadeFarZ[C];A[0].set(-1,-1,F);A[1].set(1,-1,F);A[2].set(-1,1,F);A[3].set(1,1,F);A[4].set(-1,-1,B);A[5].set(1,-1,B);A[6].set(-1,1,B);A[7].set(1,1,B);H.originalCamera=v;A=new THREE.Gyroscope;A.position.copy(x.shadowCascadeOffset);A.add(H);A.add(H.target);v.add(A);x.shadowCascadeArray[E]=H;console.log("Created virtualLight",H)}C= +x;F=E;B=C.shadowCascadeArray[F];B.position.copy(C.position);B.target.position.copy(C.target.position);B.lookAt(B.target);B.shadowCameraVisible=C.shadowCameraVisible;B.shadowDarkness=C.shadowDarkness;B.shadowBias=C.shadowCascadeBias[F];A=C.shadowCascadeNearZ[F];C=C.shadowCascadeFarZ[F];B=B.pointsFrustum;B[0].z=A;B[1].z=A;B[2].z=A;B[3].z=A;B[4].z=C;B[5].z=C;B[6].z=C;B[7].z=C;R[D]=H;D++}else R[D]=x,D++;u=0;for(K=R.length;uC;C++)F=B[C],F.copy(A[C]),F.unproject(E),F.applyMatrix4(D.matrixWorldInverse),F.xr.x&&(r.x=F.x),F.yr.y&&(r.y=F.y),F.zr.z&&(r.z=F.z);D.left=m.x;D.right=r.x;D.top=r.y;D.bottom=m.y;D.updateProjectionMatrix()}D=x.shadowMap;A=x.shadowMatrix;E=x.shadowCamera;E.position.setFromMatrixPosition(x.matrixWorld);t.setFromMatrixPosition(x.target.matrixWorld);E.lookAt(t);E.updateMatrixWorld();E.matrixWorldInverse.getInverse(E.matrixWorld);x.cameraHelper&& +(x.cameraHelper.visible=x.shadowCameraVisible);x.shadowCameraVisible&&x.cameraHelper.update();A.set(.5,0,0,.5,0,.5,0,.5,0,0,.5,.5,0,0,0,1);A.multiply(E.projectionMatrix);A.multiply(E.matrixWorldInverse);q.multiplyMatrices(E.projectionMatrix,E.matrixWorldInverse);p.setFromMatrix(q);a.setRenderTarget(D);a.clear();s.length=0;e(c,c,E);x=0;for(D=s.length;x 0 ) {\nfloat depth = gl_FragCoord.z / gl_FragCoord.w;\nfloat fogFactor = 0.0;\nif ( fogType == 1 ) {\nfogFactor = smoothstep( fogNear, fogFar, depth );\n} else {\nconst float LOG2 = 1.442695;\nfloat fogFactor = exp2( - fogDensity * fogDensity * depth * depth * LOG2 );\nfogFactor = 1.0 - clamp( fogFactor, 0.0, 1.0 );\n}\ngl_FragColor = mix( gl_FragColor, vec4( fogColor, gl_FragColor.w ), fogFactor );\n}\n}"].join("\n")); +w.compileShader(R);w.compileShader(H);w.attachShader(F,R);w.attachShader(F,H);w.linkProgram(F);D=F;v=w.getAttribLocation(D,"position");y=w.getAttribLocation(D,"uv");c=w.getUniformLocation(D,"uvOffset");d=w.getUniformLocation(D,"uvScale");e=w.getUniformLocation(D,"rotation");f=w.getUniformLocation(D,"scale");g=w.getUniformLocation(D,"color");h=w.getUniformLocation(D,"map");k=w.getUniformLocation(D,"opacity");n=w.getUniformLocation(D,"modelViewMatrix");p=w.getUniformLocation(D,"projectionMatrix");q= +w.getUniformLocation(D,"fogType");m=w.getUniformLocation(D,"fogDensity");r=w.getUniformLocation(D,"fogNear");t=w.getUniformLocation(D,"fogFar");s=w.getUniformLocation(D,"fogColor");u=w.getUniformLocation(D,"alphaTest");F=document.createElement("canvas");F.width=8;F.height=8;R=F.getContext("2d");R.fillStyle="white";R.fillRect(0,0,8,8);E=new THREE.Texture(F);E.needsUpdate=!0}w.useProgram(D);w.enableVertexAttribArray(v);w.enableVertexAttribArray(y);w.disable(w.CULL_FACE);w.enable(w.BLEND);w.bindBuffer(w.ARRAY_BUFFER, +K);w.vertexAttribPointer(v,2,w.FLOAT,!1,16,0);w.vertexAttribPointer(y,2,w.FLOAT,!1,16,8);w.bindBuffer(w.ELEMENT_ARRAY_BUFFER,x);w.uniformMatrix4fv(p,!1,B.projectionMatrix.elements);w.activeTexture(w.TEXTURE0);w.uniform1i(h,0);R=F=0;(H=A.fog)?(w.uniform3f(s,H.color.r,H.color.g,H.color.b),H instanceof THREE.Fog?(w.uniform1f(r,H.near),w.uniform1f(t,H.far),w.uniform1i(q,1),R=F=1):H instanceof THREE.FogExp2&&(w.uniform1f(m,H.density),w.uniform1i(q,2),R=F=2)):(w.uniform1i(q,0),R=F=0);for(var H=0,C=b.length;H< +C;H++){var T=b[H];T._modelViewMatrix.multiplyMatrices(B.matrixWorldInverse,T.matrixWorld);T.z=null===T.renderDepth?-T._modelViewMatrix.elements[14]:T.renderDepth}b.sort(G);for(var Q=[],H=0,C=b.length;Hq-1?0:q-1,r=q+1>e-1?e-1:q+1,t=0>p-1?0:p-1,s=p+1>d-1?d-1:p+1,u=[],v=[0,0,h[4*(q*d+p)]/255*b];u.push([-1,0,h[4*(q*d+t)]/255*b]);u.push([-1,-1,h[4*(m*d+t)]/255*b]);u.push([0,-1,h[4*(m*d+p)]/255*b]);u.push([1,-1,h[4*(m*d+s)]/255*b]);u.push([1,0,h[4*(q*d+s)]/255*b]);u.push([1,1,h[4*(r*d+s)]/255*b]);u.push([0,1,h[4*(r*d+p)]/255* +b]);u.push([-1,1,h[4*(r*d+t)]/255*b]);m=[];t=u.length;for(r=0;re)return null;var f=[],g=[],h=[],k,n,p;if(0=q--){console.log("Warning, unable to triangulate polygon!");break}k=n;e<=k&&(k=0);n=k+1;e<=n&&(n=0);p=n+1;e<=p&&(p=0);var m;a:{var r=m=void 0,t=void 0,s=void 0,u=void 0,v=void 0,y=void 0,G=void 0,w=void 0, +r=a[g[k]].x,t=a[g[k]].y,s=a[g[n]].x,u=a[g[n]].y,v=a[g[p]].x,y=a[g[p]].y;if(1E-10>(s-r)*(y-t)-(u-t)*(v-r))m=!1;else{var K=void 0,x=void 0,D=void 0,E=void 0,A=void 0,B=void 0,F=void 0,R=void 0,H=void 0,C=void 0,H=R=F=w=G=void 0,K=v-s,x=y-u,D=r-v,E=t-y,A=s-r,B=u-t;for(m=0;mk)g=d+1;else if(0b&&(b=0);1=b)return b=c[a]-b,a=this.curves[a],b=1-b/a.getLength(),a.getPointAt(b);a++}return null};THREE.CurvePath.prototype.getLength=function(){var a=this.getCurveLengths();return a[a.length-1]}; +THREE.CurvePath.prototype.getCurveLengths=function(){if(this.cacheLengths&&this.cacheLengths.length==this.curves.length)return this.cacheLengths;var a=[],b=0,c,d=this.curves.length;for(c=0;cb?b=h.x:h.xc?c=h.y:h.yd?d=h.z:h.zMath.abs(d.x-c[0].x)&&1E-10>Math.abs(d.y-c[0].y)&&c.splice(c.length-1,1);b&&c.push(c[0]);return c}; +THREE.Path.prototype.toShapes=function(a,b){function c(a){for(var b=[],c=0,d=a.length;cm&&(g=b[f],k=-k,h=b[e],m=-m),!(a.yh.y))if(a.y==g.y){if(a.x==g.x)return!0}else{e=m*(a.x-g.x)-k*(a.y-g.y);if(0==e)return!0;0>e||(d=!d)}}else if(a.y==g.y&&(h.x<=a.x&&a.x<=g.x||g.x<=a.x&&a.x<= +h.x))return!0}return d}var e=function(a){var b,c,d,e,f=[],g=new THREE.Path;b=0;for(c=a.length;bE||E>D)return[];k=n*p-k*q;if(0>k||k>D)return[]}else{if(0d?[]:k==d?f?[]:[g]:a<=d?[g,h]: +[g,n]}function e(a,b,c,d){var e=b.x-a.x,f=b.y-a.y;b=c.x-a.x;c=c.y-a.y;var g=d.x-a.x;d=d.y-a.y;a=e*c-f*b;e=e*d-f*g;return 1E-10f&&(f=d);var g=a+1;g>d&&(g=0);d=e(h[a],h[f],h[g],k[b]);if(!d)return!1; +d=k.length-1;f=b-1;0>f&&(f=d);g=b+1;g>d&&(g=0);return(d=e(k[b],k[f],k[g],h[a]))?!0:!1}function f(a,b){var c,e;for(c=0;cC){console.log("Infinite Loop! Holes left:"+ +n.length+", Probably Hole outside Shape!");break}for(q=B;qh;h++)n=k[h].x+":"+k[h].y, +n=p[n],void 0!==n&&(k[h]=n);return q.concat()},isClockWise:function(a){return 0>THREE.FontUtils.Triangulate.area(a)},b2p0:function(a,b){var c=1-a;return c*c*b},b2p1:function(a,b){return 2*(1-a)*a*b},b2p2:function(a,b){return a*a*b},b2:function(a,b,c,d){return this.b2p0(a,b)+this.b2p1(a,c)+this.b2p2(a,d)},b3p0:function(a,b){var c=1-a;return c*c*c*b},b3p1:function(a,b){var c=1-a;return 3*c*c*a*b},b3p2:function(a,b){return 3*(1-a)*a*a*b},b3p3:function(a,b){return a*a*a*b},b3:function(a,b,c,d,e){return this.b3p0(a, +b)+this.b3p1(a,c)+this.b3p2(a,d)+this.b3p3(a,e)}};THREE.LineCurve=function(a,b){this.v1=a;this.v2=b};THREE.LineCurve.prototype=Object.create(THREE.Curve.prototype);THREE.LineCurve.prototype.getPoint=function(a){var b=this.v2.clone().sub(this.v1);b.multiplyScalar(a).add(this.v1);return b};THREE.LineCurve.prototype.getPointAt=function(a){return this.getPoint(a)};THREE.LineCurve.prototype.getTangent=function(a){return this.v2.clone().sub(this.v1).normalize()}; +THREE.QuadraticBezierCurve=function(a,b,c){this.v0=a;this.v1=b;this.v2=c};THREE.QuadraticBezierCurve.prototype=Object.create(THREE.Curve.prototype);THREE.QuadraticBezierCurve.prototype.getPoint=function(a){var b=new THREE.Vector2;b.x=THREE.Shape.Utils.b2(a,this.v0.x,this.v1.x,this.v2.x);b.y=THREE.Shape.Utils.b2(a,this.v0.y,this.v1.y,this.v2.y);return b}; +THREE.QuadraticBezierCurve.prototype.getTangent=function(a){var b=new THREE.Vector2;b.x=THREE.Curve.Utils.tangentQuadraticBezier(a,this.v0.x,this.v1.x,this.v2.x);b.y=THREE.Curve.Utils.tangentQuadraticBezier(a,this.v0.y,this.v1.y,this.v2.y);return b.normalize()};THREE.CubicBezierCurve=function(a,b,c,d){this.v0=a;this.v1=b;this.v2=c;this.v3=d};THREE.CubicBezierCurve.prototype=Object.create(THREE.Curve.prototype); +THREE.CubicBezierCurve.prototype.getPoint=function(a){var b;b=THREE.Shape.Utils.b3(a,this.v0.x,this.v1.x,this.v2.x,this.v3.x);a=THREE.Shape.Utils.b3(a,this.v0.y,this.v1.y,this.v2.y,this.v3.y);return new THREE.Vector2(b,a)};THREE.CubicBezierCurve.prototype.getTangent=function(a){var b;b=THREE.Curve.Utils.tangentCubicBezier(a,this.v0.x,this.v1.x,this.v2.x,this.v3.x);a=THREE.Curve.Utils.tangentCubicBezier(a,this.v0.y,this.v1.y,this.v2.y,this.v3.y);b=new THREE.Vector2(b,a);b.normalize();return b}; +THREE.SplineCurve=function(a){this.points=void 0==a?[]:a};THREE.SplineCurve.prototype=Object.create(THREE.Curve.prototype);THREE.SplineCurve.prototype.getPoint=function(a){var b=this.points;a*=b.length-1;var c=Math.floor(a);a-=c;var d=b[0==c?c:c-1],e=b[c],f=b[c>b.length-2?b.length-1:c+1],b=b[c>b.length-3?b.length-1:c+2],c=new THREE.Vector2;c.x=THREE.Curve.Utils.interpolate(d.x,e.x,f.x,b.x,a);c.y=THREE.Curve.Utils.interpolate(d.y,e.y,f.y,b.y,a);return c}; +THREE.EllipseCurve=function(a,b,c,d,e,f,g){this.aX=a;this.aY=b;this.xRadius=c;this.yRadius=d;this.aStartAngle=e;this.aEndAngle=f;this.aClockwise=g};THREE.EllipseCurve.prototype=Object.create(THREE.Curve.prototype); +THREE.EllipseCurve.prototype.getPoint=function(a){var b=this.aEndAngle-this.aStartAngle;0>b&&(b+=2*Math.PI);b>2*Math.PI&&(b-=2*Math.PI);a=!0===this.aClockwise?this.aEndAngle+(1-a)*(2*Math.PI-b):this.aStartAngle+a*b;b=new THREE.Vector2;b.x=this.aX+this.xRadius*Math.cos(a);b.y=this.aY+this.yRadius*Math.sin(a);return b};THREE.ArcCurve=function(a,b,c,d,e,f){THREE.EllipseCurve.call(this,a,b,c,c,d,e,f)};THREE.ArcCurve.prototype=Object.create(THREE.EllipseCurve.prototype); +THREE.LineCurve3=THREE.Curve.create(function(a,b){this.v1=a;this.v2=b},function(a){var b=new THREE.Vector3;b.subVectors(this.v2,this.v1);b.multiplyScalar(a);b.add(this.v1);return b});THREE.QuadraticBezierCurve3=THREE.Curve.create(function(a,b,c){this.v0=a;this.v1=b;this.v2=c},function(a){var b=new THREE.Vector3;b.x=THREE.Shape.Utils.b2(a,this.v0.x,this.v1.x,this.v2.x);b.y=THREE.Shape.Utils.b2(a,this.v0.y,this.v1.y,this.v2.y);b.z=THREE.Shape.Utils.b2(a,this.v0.z,this.v1.z,this.v2.z);return b}); +THREE.CubicBezierCurve3=THREE.Curve.create(function(a,b,c,d){this.v0=a;this.v1=b;this.v2=c;this.v3=d},function(a){var b=new THREE.Vector3;b.x=THREE.Shape.Utils.b3(a,this.v0.x,this.v1.x,this.v2.x,this.v3.x);b.y=THREE.Shape.Utils.b3(a,this.v0.y,this.v1.y,this.v2.y,this.v3.y);b.z=THREE.Shape.Utils.b3(a,this.v0.z,this.v1.z,this.v2.z,this.v3.z);return b}); +THREE.SplineCurve3=THREE.Curve.create(function(a){this.points=void 0==a?[]:a},function(a){var b=this.points;a*=b.length-1;var c=Math.floor(a);a-=c;var d=b[0==c?c:c-1],e=b[c],f=b[c>b.length-2?b.length-1:c+1],b=b[c>b.length-3?b.length-1:c+2],c=new THREE.Vector3;c.x=THREE.Curve.Utils.interpolate(d.x,e.x,f.x,b.x,a);c.y=THREE.Curve.Utils.interpolate(d.y,e.y,f.y,b.y,a);c.z=THREE.Curve.Utils.interpolate(d.z,e.z,f.z,b.z,a);return c}); +THREE.ClosedSplineCurve3=THREE.Curve.create(function(a){this.points=void 0==a?[]:a},function(a){var b=this.points;a*=b.length-0;var c=Math.floor(a);a-=c;var c=c+(0a.hierarchy[b].keys[c].time&&(a.hierarchy[b].keys[c].time= +0),void 0!==a.hierarchy[b].keys[c].rot&&!(a.hierarchy[b].keys[c].rot instanceof THREE.Quaternion)){var d=a.hierarchy[b].keys[c].rot;a.hierarchy[b].keys[c].rot=(new THREE.Quaternion).fromArray(d)}if(a.hierarchy[b].keys.length&&void 0!==a.hierarchy[b].keys[0].morphTargets){d={};for(c=0;cd;d++){for(var e=this.keyTypes[d],f=this.data.hierarchy[a].keys[0],g=this.getNextKeyWith(e,a,1);g.timef.index;)f=g,g=this.getNextKeyWith(e,a,g.index+1);c.prevKey[e]=f;c.nextKey[e]=g}}}; +THREE.Animation.prototype.resetBlendWeights=function(){for(var a=0,b=this.hierarchy.length;aa.length-2?q:q+1;c[3]=q>a.length-3?q:q+2;q=a[c[0]];r=a[c[1]];t=a[c[2]];s=a[c[3]];c=e*e;m=e*c;d[0]=f(q[0],r[0],t[0],s[0],e,c,m);d[1]=f(q[1],r[1],t[1],s[1],e,c,m);d[2]=f(q[2],r[2],t[2],s[2],e,c,m);return d},f=function(a,b,c,d,e,f,m){a=.5*(c-a);d=.5*(d-b);return(2*(b-c)+a+d)*m+ +(-3*(b-c)-2*a-d)*f+a*e+b};return function(f){if(!1!==this.isPlaying&&(this.currentTime+=f*this.timeScale,0!==this.weight)){f=this.data.length;if(this.currentTime>f||0>this.currentTime)if(this.loop)this.currentTime%=f,0>this.currentTime&&(this.currentTime+=f),this.reset();else{this.stop();return}f=0;for(var h=this.hierarchy.length;fq;q++){var m=this.keyTypes[q],r=n.prevKey[m],t=n.nextKey[m]; +if(0this.timeScale&&r.time>=this.currentTime){r=this.data.hierarchy[f].keys[0];for(t=this.getNextKeyWith(m,f,1);t.timer.index;)r=t,t=this.getNextKeyWith(m,f,t.index+1);n.prevKey[m]=r;n.nextKey[m]=t}k.matrixAutoUpdate=!0;k.matrixWorldNeedsUpdate=!0;var s=(this.currentTime-r.time)/(t.time-r.time),u=r[m],v=t[m];0>s&&(s=0);1a&&(this.currentTime%=a);this.currentTime=Math.min(this.currentTime,a);a=0;for(var b=this.hierarchy.length;af.index;)f=g,g=e[f.index+1];d.prevKey= +f;d.nextKey=g}g.time>=this.currentTime?f.interpolate(g,this.currentTime):f.interpolate(g,g.time);this.data.hierarchy[a].node.updateMatrix();c.matrixWorldNeedsUpdate=!0}}}};THREE.KeyFrameAnimation.prototype.getNextKeyWith=function(a,b,c){b=this.data.hierarchy[b].keys;for(c%=b.length;cthis.duration&&(this.currentTime%=this.duration);this.currentTime=Math.min(this.currentTime,this.duration);c=this.duration/this.frames;var d=Math.floor(this.currentTime/c);d!=b&&(this.mesh.morphTargetInfluences[a]=0,this.mesh.morphTargetInfluences[b]=1,this.mesh.morphTargetInfluences[d]= +0,a=b,b=d);this.mesh.morphTargetInfluences[d]=this.currentTime%c/c;this.mesh.morphTargetInfluences[a]=1-this.mesh.morphTargetInfluences[d]}}}()}; +THREE.BoxGeometry=function(a,b,c,d,e,f){function g(a,b,c,d,e,f,g,s){var u,v=h.widthSegments,y=h.heightSegments,G=e/2,w=f/2,K=h.vertices.length;if("x"===a&&"y"===b||"y"===a&&"x"===b)u="z";else if("x"===a&&"z"===b||"z"===a&&"x"===b)u="y",y=h.depthSegments;else if("z"===a&&"y"===b||"y"===a&&"z"===b)u="x",v=h.depthSegments;var x=v+1,D=y+1,E=e/v,A=f/y,B=new THREE.Vector3;B[u]=0=d)return new THREE.Vector2(c,a);d=Math.sqrt(d/2)}else a=!1,1E-10d?-1E-10>f&&(a=!0):Math.sign(e)== +Math.sign(g)&&(a=!0),a?(c=-e,a=d,d=Math.sqrt(h)):(c=d,a=e,d=Math.sqrt(h/2));return new THREE.Vector2(c/d,a/d)}function e(a,b){var c,d;for(P=a.length;0<=--P;){c=P;d=P-1;0>d&&(d=a.length-1);for(var e=0,f=r+2*p,e=0;eMath.abs(b.y-c.y)?[new THREE.Vector2(b.x,1-b.z),new THREE.Vector2(c.x,1-c.z),new THREE.Vector2(d.x,1-d.z),new THREE.Vector2(e.x,1-e.z)]:[new THREE.Vector2(b.y,1-b.z),new THREE.Vector2(c.y,1-c.z),new THREE.Vector2(d.y, +1-d.z),new THREE.Vector2(e.y,1-e.z)]}};THREE.ShapeGeometry=function(a,b){THREE.Geometry.call(this);this.type="ShapeGeometry";!1===a instanceof Array&&(a=[a]);this.addShapeList(a,b);this.computeFaceNormals()};THREE.ShapeGeometry.prototype=Object.create(THREE.Geometry.prototype);THREE.ShapeGeometry.prototype.addShapeList=function(a,b){for(var c=0,d=a.length;cc&&1===a.x&&(a=new THREE.Vector2(a.x-1,a.y));0===b.x&&0===b.z&&(a=new THREE.Vector2(c/2/Math.PI+.5, +a.y));return a.clone()}THREE.Geometry.call(this);this.type="PolyhedronGeometry";this.parameters={vertices:a,indices:b,radius:c,detail:d};c=c||1;d=d||0;for(var k=this,n=0,p=a.length;nr&&(.2>d&&(b[0].x+=1),.2>a&&(b[1].x+=1),.2>q&&(b[2].x+=1));n=0;for(p=this.vertices.length;nc.y?this.quaternion.set(1,0,0,0):(a.set(c.z,0,-c.x).normalize(),b=Math.acos(c.y),this.quaternion.setFromAxisAngle(a,b))}}(); +THREE.ArrowHelper.prototype.setLength=function(a,b,c){void 0===b&&(b=.2*a);void 0===c&&(c=.2*b);this.line.scale.set(1,a,1);this.line.updateMatrix();this.cone.scale.set(c,b,c);this.cone.position.y=a;this.cone.updateMatrix()};THREE.ArrowHelper.prototype.setColor=function(a){this.line.material.color.set(a);this.cone.material.color.set(a)}; +THREE.BoxHelper=function(a){var b=new THREE.BufferGeometry;b.addAttribute("position",new THREE.BufferAttribute(new Float32Array(72),3));THREE.Line.call(this,b,new THREE.LineBasicMaterial({color:16776960}),THREE.LinePieces);void 0!==a&&this.update(a)};THREE.BoxHelper.prototype=Object.create(THREE.Line.prototype); +THREE.BoxHelper.prototype.update=function(a){var b=a.geometry;null===b.boundingBox&&b.computeBoundingBox();var c=b.boundingBox.min,b=b.boundingBox.max,d=this.geometry.attributes.position.array;d[0]=b.x;d[1]=b.y;d[2]=b.z;d[3]=c.x;d[4]=b.y;d[5]=b.z;d[6]=c.x;d[7]=b.y;d[8]=b.z;d[9]=c.x;d[10]=c.y;d[11]=b.z;d[12]=c.x;d[13]=c.y;d[14]=b.z;d[15]=b.x;d[16]=c.y;d[17]=b.z;d[18]=b.x;d[19]=c.y;d[20]=b.z;d[21]=b.x;d[22]=b.y;d[23]=b.z;d[24]=b.x;d[25]=b.y;d[26]=c.z;d[27]=c.x;d[28]=b.y;d[29]=c.z;d[30]=c.x;d[31]=b.y; +d[32]=c.z;d[33]=c.x;d[34]=c.y;d[35]=c.z;d[36]=c.x;d[37]=c.y;d[38]=c.z;d[39]=b.x;d[40]=c.y;d[41]=c.z;d[42]=b.x;d[43]=c.y;d[44]=c.z;d[45]=b.x;d[46]=b.y;d[47]=c.z;d[48]=b.x;d[49]=b.y;d[50]=b.z;d[51]=b.x;d[52]=b.y;d[53]=c.z;d[54]=c.x;d[55]=b.y;d[56]=b.z;d[57]=c.x;d[58]=b.y;d[59]=c.z;d[60]=c.x;d[61]=c.y;d[62]=b.z;d[63]=c.x;d[64]=c.y;d[65]=c.z;d[66]=b.x;d[67]=c.y;d[68]=b.z;d[69]=b.x;d[70]=c.y;d[71]=c.z;this.geometry.attributes.position.needsUpdate=!0;this.geometry.computeBoundingSphere();this.matrix=a.matrixWorld; +this.matrixAutoUpdate=!1};THREE.BoundingBoxHelper=function(a,b){var c=void 0!==b?b:8947848;this.object=a;this.box=new THREE.Box3;THREE.Mesh.call(this,new THREE.BoxGeometry(1,1,1),new THREE.MeshBasicMaterial({color:c,wireframe:!0}))};THREE.BoundingBoxHelper.prototype=Object.create(THREE.Mesh.prototype);THREE.BoundingBoxHelper.prototype.update=function(){this.box.setFromObject(this.object);this.box.size(this.scale);this.box.center(this.position)}; +THREE.CameraHelper=function(a){function b(a,b,d){c(a,d);c(b,d)}function c(a,b){d.vertices.push(new THREE.Vector3);d.colors.push(new THREE.Color(b));void 0===f[a]&&(f[a]=[]);f[a].push(d.vertices.length-1)}var d=new THREE.Geometry,e=new THREE.LineBasicMaterial({color:16777215,vertexColors:THREE.FaceColors}),f={};b("n1","n2",16755200);b("n2","n4",16755200);b("n4","n3",16755200);b("n3","n1",16755200);b("f1","f2",16755200);b("f2","f4",16755200);b("f4","f3",16755200);b("f3","f1",16755200);b("n1","f1",16755200); +b("n2","f2",16755200);b("n3","f3",16755200);b("n4","f4",16755200);b("p","n1",16711680);b("p","n2",16711680);b("p","n3",16711680);b("p","n4",16711680);b("u1","u2",43775);b("u2","u3",43775);b("u3","u1",43775);b("c","t",16777215);b("p","c",3355443);b("cn1","cn2",3355443);b("cn3","cn4",3355443);b("cf1","cf2",3355443);b("cf3","cf4",3355443);THREE.Line.call(this,d,e,THREE.LinePieces);this.camera=a;this.matrix=a.matrixWorld;this.matrixAutoUpdate=!1;this.pointMap=f;this.update()}; +THREE.CameraHelper.prototype=Object.create(THREE.Line.prototype); +THREE.CameraHelper.prototype.update=function(){var a,b,c=new THREE.Vector3,d=new THREE.Camera,e=function(e,g,h,k){c.set(g,h,k).unproject(d);e=b[e];if(void 0!==e)for(g=0,h=e.length;gt;t++){d[0]=r[g[t]];d[1]=r[g[(t+1)%3]];d.sort(f);var s=d.toString();void 0===e[s]?(e[s]={vert1:d[0],vert2:d[1],face1:q,face2:void 0},p++):e[s].face2=q}d=new Float32Array(6*p);f=0;for(s in e)if(g=e[s],void 0===g.face2|| +.9999>k[g.face1].normal.dot(k[g.face2].normal))p=n[g.vert1],d[f++]=p.x,d[f++]=p.y,d[f++]=p.z,p=n[g.vert2],d[f++]=p.x,d[f++]=p.y,d[f++]=p.z;h.addAttribute("position",new THREE.BufferAttribute(d,3));THREE.Line.call(this,h,new THREE.LineBasicMaterial({color:c}),THREE.LinePieces);this.matrix=a.matrixWorld;this.matrixAutoUpdate=!1};THREE.EdgesHelper.prototype=Object.create(THREE.Line.prototype); +THREE.FaceNormalsHelper=function(a,b,c,d){this.object=a;this.size=void 0!==b?b:1;a=void 0!==c?c:16776960;d=void 0!==d?d:1;b=new THREE.Geometry;c=0;for(var e=this.object.geometry.faces.length;cb;b++)a.faces[b].color=this.colors[4>b?0:1];b=new THREE.MeshBasicMaterial({vertexColors:THREE.FaceColors,wireframe:!0});this.lightSphere=new THREE.Mesh(a,b);this.add(this.lightSphere); +this.update()};THREE.HemisphereLightHelper.prototype=Object.create(THREE.Object3D.prototype);THREE.HemisphereLightHelper.prototype.dispose=function(){this.lightSphere.geometry.dispose();this.lightSphere.material.dispose()}; +THREE.HemisphereLightHelper.prototype.update=function(){var a=new THREE.Vector3;return function(){this.colors[0].copy(this.light.color).multiplyScalar(this.light.intensity);this.colors[1].copy(this.light.groundColor).multiplyScalar(this.light.intensity);this.lightSphere.lookAt(a.setFromMatrixPosition(this.light.matrixWorld).negate());this.lightSphere.geometry.colorsNeedUpdate=!0}}(); +THREE.PointLightHelper=function(a,b){this.light=a;this.light.updateMatrixWorld();var c=new THREE.SphereGeometry(b,4,2),d=new THREE.MeshBasicMaterial({wireframe:!0,fog:!1});d.color.copy(this.light.color).multiplyScalar(this.light.intensity);THREE.Mesh.call(this,c,d);this.matrix=this.light.matrixWorld;this.matrixAutoUpdate=!1};THREE.PointLightHelper.prototype=Object.create(THREE.Mesh.prototype);THREE.PointLightHelper.prototype.dispose=function(){this.geometry.dispose();this.material.dispose()}; +THREE.PointLightHelper.prototype.update=function(){this.material.color.copy(this.light.color).multiplyScalar(this.light.intensity)}; +THREE.SkeletonHelper=function(a){this.bones=this.getBoneList(a);for(var b=new THREE.Geometry,c=0;cs;s++){d[0]=t[g[s]];d[1]=t[g[(s+1)%3]];d.sort(f);var u=d.toString();void 0===e[u]&&(q[2*p]=d[0],q[2*p+1]=d[1],e[u]=!0,p++)}d=new Float32Array(6*p);m=0;for(r=p;ms;s++)p= +k[q[2*m+s]],g=6*m+3*s,d[g+0]=p.x,d[g+1]=p.y,d[g+2]=p.z;h.addAttribute("position",new THREE.BufferAttribute(d,3))}else if(a.geometry instanceof THREE.BufferGeometry){if(void 0!==a.geometry.attributes.index){k=a.geometry.attributes.position.array;r=a.geometry.attributes.index.array;n=a.geometry.drawcalls;p=0;0===n.length&&(n=[{count:r.length,index:0,start:0}]);for(var q=new Uint32Array(2*r.length),t=0,v=n.length;ts;s++)d[0]= +g+r[m+s],d[1]=g+r[m+(s+1)%3],d.sort(f),u=d.toString(),void 0===e[u]&&(q[2*p]=d[0],q[2*p+1]=d[1],e[u]=!0,p++);d=new Float32Array(6*p);m=0;for(r=p;ms;s++)g=6*m+3*s,p=3*q[2*m+s],d[g+0]=k[p],d[g+1]=k[p+1],d[g+2]=k[p+2]}else for(k=a.geometry.attributes.position.array,p=k.length/3,q=p/3,d=new Float32Array(6*p),m=0,r=q;ms;s++)g=18*m+6*s,q=9*m+3*s,d[g+0]=k[q],d[g+1]=k[q+1],d[g+2]=k[q+2],p=9*m+(s+1)%3*3,d[g+3]=k[p],d[g+4]=k[p+1],d[g+5]=k[p+2];h.addAttribute("position",new THREE.BufferAttribute(d, +3))}THREE.Line.call(this,h,new THREE.LineBasicMaterial({color:c}),THREE.LinePieces);this.matrix=a.matrixWorld;this.matrixAutoUpdate=!1};THREE.WireframeHelper.prototype=Object.create(THREE.Line.prototype);THREE.ImmediateRenderObject=function(){THREE.Object3D.call(this);this.render=function(a){}};THREE.ImmediateRenderObject.prototype=Object.create(THREE.Object3D.prototype); +THREE.MorphBlendMesh=function(a,b){THREE.Mesh.call(this,a,b);this.animationsMap={};this.animationsList=[];var c=this.geometry.morphTargets.length;this.createAnimation("__default",0,c-1,c/1);this.setAnimationWeight("__default",1)};THREE.MorphBlendMesh.prototype=Object.create(THREE.Mesh.prototype); +THREE.MorphBlendMesh.prototype.createAnimation=function(a,b,c,d){b={startFrame:b,endFrame:c,length:c-b+1,fps:d,duration:(c-b)/d,lastFrame:0,currentFrame:0,active:!1,time:0,direction:1,weight:1,directionBackwards:!1,mirroredLoop:!1};this.animationsMap[a]=b;this.animationsList.push(b)}; +THREE.MorphBlendMesh.prototype.autoCreateAnimations=function(a){for(var b=/([a-z]+)_?(\d+)/,c,d={},e=this.geometry,f=0,g=e.morphTargets.length;fh.end&&(h.end=f);c||(c=k)}}for(k in d)h=d[k],this.createAnimation(k,h.start,h.end,a);this.firstAnimation=c}; +THREE.MorphBlendMesh.prototype.setAnimationDirectionForward=function(a){if(a=this.animationsMap[a])a.direction=1,a.directionBackwards=!1};THREE.MorphBlendMesh.prototype.setAnimationDirectionBackward=function(a){if(a=this.animationsMap[a])a.direction=-1,a.directionBackwards=!0};THREE.MorphBlendMesh.prototype.setAnimationFPS=function(a,b){var c=this.animationsMap[a];c&&(c.fps=b,c.duration=(c.end-c.start)/c.fps)}; +THREE.MorphBlendMesh.prototype.setAnimationDuration=function(a,b){var c=this.animationsMap[a];c&&(c.duration=b,c.fps=(c.end-c.start)/c.duration)};THREE.MorphBlendMesh.prototype.setAnimationWeight=function(a,b){var c=this.animationsMap[a];c&&(c.weight=b)};THREE.MorphBlendMesh.prototype.setAnimationTime=function(a,b){var c=this.animationsMap[a];c&&(c.time=b)};THREE.MorphBlendMesh.prototype.getAnimationTime=function(a){var b=0;if(a=this.animationsMap[a])b=a.time;return b}; +THREE.MorphBlendMesh.prototype.getAnimationDuration=function(a){var b=-1;if(a=this.animationsMap[a])b=a.duration;return b};THREE.MorphBlendMesh.prototype.playAnimation=function(a){var b=this.animationsMap[a];b?(b.time=0,b.active=!0):console.warn("animation["+a+"] undefined")};THREE.MorphBlendMesh.prototype.stopAnimation=function(a){if(a=this.animationsMap[a])a.active=!1}; +THREE.MorphBlendMesh.prototype.update=function(a){for(var b=0,c=this.animationsList.length;bd.duration||0>d.time)d.direction*=-1,d.time>d.duration&&(d.time=d.duration,d.directionBackwards=!0),0>d.time&&(d.time=0,d.directionBackwards=!1)}else d.time%=d.duration,0>d.time&&(d.time+=d.duration);var f=d.startFrame+THREE.Math.clamp(Math.floor(d.time/e),0,d.length-1),g=d.weight; +f!==d.currentFrame&&(this.morphTargetInfluences[d.lastFrame]=0,this.morphTargetInfluences[d.currentFrame]=1*g,this.morphTargetInfluences[f]=0,d.lastFrame=d.currentFrame,d.currentFrame=f);e=d.time%e/e;d.directionBackwards&&(e=1-e);this.morphTargetInfluences[d.currentFrame]=e*g;this.morphTargetInfluences[d.lastFrame]=(1-e)*g}}}; diff --git a/pychaste/src/py/chaste/mesh/__init__.py b/pychaste/src/py/chaste/mesh/__init__.py new file mode 100644 index 0000000000..6252fbfced --- /dev/null +++ b/pychaste/src/py/chaste/mesh/__init__.py @@ -0,0 +1,325 @@ +"""Mesh Module""" + +__copyright__ = """Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +from chaste._pychaste_all import ( + ChasteCuboid_2, + ChasteCuboid_3, + ChasteEllipsoid_2, + ChasteEllipsoid_3, + ChastePoint_2, + ChastePoint_3, + Cylindrical2dMesh, + Cylindrical2dNodesOnlyMesh, + Cylindrical2dVertexMesh, + CylindricalHoneycombMeshGenerator, + CylindricalHoneycombVertexMeshGenerator, + Edge_2, + Edge_3, + EdgeHelper_2, + EdgeHelper_3, + EdgeOperation, + Element_2_2, + Element_3_3, + FluidSource_2, + FluidSource_3, + HoneycombMeshGenerator, + HoneycombVertexMeshGenerator, + ImmersedBoundaryElement_1_2, + ImmersedBoundaryElement_2_2, + ImmersedBoundaryElement_2_3, + ImmersedBoundaryElement_3_3, + ImmersedBoundaryHoneycombMeshGenerator, + ImmersedBoundaryMesh_2_2, + ImmersedBoundaryMesh_3_3, + ImmersedBoundaryPalisadeMeshGenerator, + MutableElement_1_2, + MutableElement_2_2, + MutableElement_2_3, + MutableElement_3_3, + MutableMesh_2_2, + MutableMesh_3_3, + MutableVertexMesh_2_2, + MutableVertexMesh_3_3, + Node_2, + Node_3, + NodeAttributes_2, + NodeAttributes_3, + NodesOnlyMesh_2, + NodesOnlyMesh_3, + PeriodicNodesOnlyMesh_2, + PeriodicNodesOnlyMesh_3, + PottsElement_2, + PottsElement_3, + PottsMesh_2, + PottsMesh_3, + PottsMeshGenerator_2, + PottsMeshGenerator_3, + PottsMeshWriter_2, + PottsMeshWriter_3, + TetrahedralMesh_2_2, + TetrahedralMesh_3_3, + Toroidal2dMesh, + Toroidal2dVertexMesh, + ToroidalHoneycombMeshGenerator, + ToroidalHoneycombVertexMeshGenerator, + VertexMesh_2_2, + VertexMesh_3_3, + VoronoiVertexMeshGenerator, +) +from chaste._syntax import DeprecatedClass, TemplateClassDict + +# Template Class Syntax +ChasteCuboid = TemplateClassDict( + { + ("2",): ChasteCuboid_2, + ("3",): ChasteCuboid_3, + } +) + +ChasteEllipsoid = TemplateClassDict( + { + ("2",): ChasteEllipsoid_2, + ("3",): ChasteEllipsoid_3, + } +) + +ChastePoint = TemplateClassDict( + { + ("2",): ChastePoint_2, + ("3",): ChastePoint_3, + } +) + +Edge = TemplateClassDict( + { + ("2",): Edge_2, + ("3",): Edge_3, + } +) + +EdgeHelper = TemplateClassDict( + { + ("2",): EdgeHelper_2, + ("3",): EdgeHelper_3, + } +) + +Element = TemplateClassDict( + { + ("2",): Element_2_2, + ("2", "2"): Element_2_2, + ("3",): Element_3_3, + ("3", "3"): Element_3_3, + } +) + +FluidSource = TemplateClassDict( + { + ("2",): FluidSource_2, + ("3",): FluidSource_3, + } +) + +ImmersedBoundaryElement = TemplateClassDict( + { + ("1", "2"): ImmersedBoundaryElement_1_2, + ("2",): ImmersedBoundaryElement_2_2, + ("2", "2"): ImmersedBoundaryElement_2_2, + ("2", "3"): ImmersedBoundaryElement_2_3, + ("3",): ImmersedBoundaryElement_3_3, + ("3", "3"): ImmersedBoundaryElement_3_3, + } +) + +ImmersedBoundaryMesh = TemplateClassDict( + { + ("2",): ImmersedBoundaryMesh_2_2, + ("2", "2"): ImmersedBoundaryMesh_2_2, + ("3",): ImmersedBoundaryMesh_3_3, + ("3", "3"): ImmersedBoundaryMesh_3_3, + } +) + +MutableElement = TemplateClassDict( + { + ("1", "2"): MutableElement_1_2, + ("2",): MutableElement_2_2, + ("2", "2"): MutableElement_2_2, + ("2", "3"): MutableElement_2_3, + ("3",): MutableElement_3_3, + ("3", "3"): MutableElement_3_3, + } +) + +MutableMesh = TemplateClassDict( + { + ("2",): MutableMesh_2_2, + ("2", "2"): MutableMesh_2_2, + ("3",): MutableMesh_3_3, + ("3", "3"): MutableMesh_3_3, + } +) + +MutableVertexMesh = TemplateClassDict( + { + ("2",): MutableVertexMesh_2_2, + ("2", "2"): MutableVertexMesh_2_2, + ("3",): MutableVertexMesh_3_3, + ("3", "3"): MutableVertexMesh_3_3, + } +) + +Node = TemplateClassDict( + { + ("2",): Node_2, + ("3",): Node_3, + } +) + +NodeAttributes = TemplateClassDict( + { + ("2",): NodeAttributes_2, + ("3",): NodeAttributes_3, + } +) + +NodesOnlyMesh = TemplateClassDict( + { + ("2",): NodesOnlyMesh_2, + ("3",): NodesOnlyMesh_3, + } +) + +PeriodicNodesOnlyMesh = TemplateClassDict( + { + ("2",): PeriodicNodesOnlyMesh_2, + ("3",): PeriodicNodesOnlyMesh_3, + } +) + +PottsElement = TemplateClassDict( + { + ("2",): PottsElement_2, + ("3",): PottsElement_3, + } +) + +PottsMesh = TemplateClassDict( + { + ("2",): PottsMesh_2, + ("3",): PottsMesh_3, + } +) + +PottsMeshGenerator = TemplateClassDict( + { + ("2",): PottsMeshGenerator_2, + ("3",): PottsMeshGenerator_3, + } +) + +PottsMeshWriter = TemplateClassDict( + { + ("2",): PottsMeshWriter_2, + ("3",): PottsMeshWriter_3, + } +) + +TetrahedralMesh = TemplateClassDict( + { + ("2",): TetrahedralMesh_2_2, + ("2", "2"): TetrahedralMesh_2_2, + ("3",): TetrahedralMesh_3_3, + ("3", "3"): TetrahedralMesh_3_3, + } +) + +VertexMesh = TemplateClassDict( + { + ("2",): VertexMesh_2_2, + ("2", "2"): VertexMesh_2_2, + ("3",): VertexMesh_3_3, + ("3", "3"): VertexMesh_3_3, + } +) + +# Deprecated Class Syntax +ChasteCuboid2 = DeprecatedClass("ChasteCuboid2", ChasteCuboid_2) +ChasteCuboid3 = DeprecatedClass("ChasteCuboid3", ChasteCuboid_3) +ChasteEllipsoid2 = DeprecatedClass("ChasteEllipsoid2", ChasteEllipsoid_2) +ChasteEllipsoid3 = DeprecatedClass("ChasteEllipsoid3", ChasteEllipsoid_3) +ChastePoint2 = DeprecatedClass("ChastePoint2", ChastePoint_2) +ChastePoint3 = DeprecatedClass("ChastePoint3", ChastePoint_3) +Edge2 = DeprecatedClass("Edge2", Edge_2) +Edge3 = DeprecatedClass("Edge3", Edge_3) +EdgeHelper2 = DeprecatedClass("EdgeHelper2", EdgeHelper_2) +EdgeHelper3 = DeprecatedClass("EdgeHelper3", EdgeHelper_3) +Element2_2 = DeprecatedClass("Element2_2", Element_2_2) +Element3_3 = DeprecatedClass("Element3_3", Element_3_3) +FluidSource2 = DeprecatedClass("FluidSource2", FluidSource_2) +FluidSource3 = DeprecatedClass("FluidSource3", FluidSource_3) +ImmersedBoundaryElement1_2 = DeprecatedClass("ImmersedBoundaryElement1_2", ImmersedBoundaryElement_1_2) +ImmersedBoundaryElement2_2 = DeprecatedClass("ImmersedBoundaryElement2_2", ImmersedBoundaryElement_2_2) +ImmersedBoundaryElement2_3 = DeprecatedClass("ImmersedBoundaryElement2_3", ImmersedBoundaryElement_2_3) +ImmersedBoundaryElement3_3 = DeprecatedClass("ImmersedBoundaryElement3_3", ImmersedBoundaryElement_3_3) +ImmersedBoundaryMesh2_2 = DeprecatedClass("ImmersedBoundaryMesh2_2", ImmersedBoundaryMesh_2_2) +ImmersedBoundaryMesh3_3 = DeprecatedClass("ImmersedBoundaryMesh3_3", ImmersedBoundaryMesh_3_3) +MutableElement1_2 = DeprecatedClass("MutableElement1_2", MutableElement_1_2) +MutableElement2_2 = DeprecatedClass("MutableElement2_2", MutableElement_2_2) +MutableElement2_3 = DeprecatedClass("MutableElement2_3", MutableElement_2_3) +MutableElement3_3 = DeprecatedClass("MutableElement3_3", MutableElement_3_3) +MutableMesh2_2 = DeprecatedClass("MutableMesh2_2", MutableMesh_2_2) +MutableMesh3_3 = DeprecatedClass("MutableMesh3_3", MutableMesh_3_3) +MutableVertexMesh2_2 = DeprecatedClass("MutableVertexMesh2_2", MutableVertexMesh_2_2) +MutableVertexMesh3_3 = DeprecatedClass("MutableVertexMesh3_3", MutableVertexMesh_3_3) +Node2 = DeprecatedClass("Node2", Node_2) +Node3 = DeprecatedClass("Node3", Node_3) +NodeAttributes2 = DeprecatedClass("NodeAttributes2", NodeAttributes_2) +NodeAttributes3 = DeprecatedClass("NodeAttributes3", NodeAttributes_3) +NodesOnlyMesh2 = DeprecatedClass("NodesOnlyMesh2", NodesOnlyMesh_2) +NodesOnlyMesh3 = DeprecatedClass("NodesOnlyMesh3", NodesOnlyMesh_3) +PeriodicNodesOnlyMesh2 = DeprecatedClass("PeriodicNodesOnlyMesh2", PeriodicNodesOnlyMesh_2) +PeriodicNodesOnlyMesh3 = DeprecatedClass("PeriodicNodesOnlyMesh3", PeriodicNodesOnlyMesh_3) +PottsElement2 = DeprecatedClass("PottsElement2", PottsElement_2) +PottsElement3 = DeprecatedClass("PottsElement3", PottsElement_3) +PottsMesh2 = DeprecatedClass("PottsMesh2", PottsMesh_2) +PottsMesh3 = DeprecatedClass("PottsMesh3", PottsMesh_3) +PottsMeshGenerator2 = DeprecatedClass("PottsMeshGenerator2", PottsMeshGenerator_2) +PottsMeshGenerator3 = DeprecatedClass("PottsMeshGenerator3", PottsMeshGenerator_3) +PottsMeshWriter2 = DeprecatedClass("PottsMeshWriter2", PottsMeshWriter_2) +PottsMeshWriter3 = DeprecatedClass("PottsMeshWriter3", PottsMeshWriter_3) +TetrahedralMesh2_2 = DeprecatedClass("TetrahedralMesh2_2", TetrahedralMesh_2_2) +TetrahedralMesh3_3 = DeprecatedClass("TetrahedralMesh3_3", TetrahedralMesh_3_3) +VertexMesh2_2 = DeprecatedClass("VertexMesh2_2", VertexMesh_2_2) +VertexMesh3_3 = DeprecatedClass("VertexMesh3_3", VertexMesh_3_3) \ No newline at end of file diff --git a/pychaste/src/py/chaste/ode/__init__.py b/pychaste/src/py/chaste/ode/__init__.py new file mode 100644 index 0000000000..112a2c38e0 --- /dev/null +++ b/pychaste/src/py/chaste/ode/__init__.py @@ -0,0 +1,42 @@ +"""ODE Module""" + +__copyright__ = """Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +from chaste._pychaste_all import ( + Alarcon2004OxygenBasedCellCycleOdeSystem, + DeltaNotchEdgeOdeSystem, + DeltaNotchInteriorOdeSystem, + DeltaNotchOdeSystem, + Goldbeter1991OdeSystem, + TysonNovak2001OdeSystem, +) diff --git a/pychaste/src/py/chaste/pde/__init__.py b/pychaste/src/py/chaste/pde/__init__.py new file mode 100644 index 0000000000..5966598c18 --- /dev/null +++ b/pychaste/src/py/chaste/pde/__init__.py @@ -0,0 +1,196 @@ +"""PDE Module""" + +__copyright__ = """Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +from chaste._pychaste_all import ( + AveragedSourceEllipticPde_2, + AveragedSourceEllipticPde_3, + AveragedSourceParabolicPde_2, + AveragedSourceParabolicPde_3, + CellBasedEllipticPdeSolver_2, + CellBasedEllipticPdeSolver_3, + CellBasedParabolicPdeSolver_2, + CellBasedParabolicPdeSolver_3, + CellwiseSourceEllipticPde_2, + CellwiseSourceEllipticPde_3, + CellwiseSourceParabolicPde_2, + CellwiseSourceParabolicPde_3, + ConstBoundaryCondition_2, + ConstBoundaryCondition_3, + EllipticBoxDomainPdeModifier_2, + EllipticBoxDomainPdeModifier_3, + EllipticGrowingDomainPdeModifier_2, + EllipticGrowingDomainPdeModifier_3, + ParabolicBoxDomainPdeModifier_2, + ParabolicBoxDomainPdeModifier_3, + ParabolicGrowingDomainPdeModifier_2, + ParabolicGrowingDomainPdeModifier_3, + PdeSimulationTime, + UniformSourceEllipticPde_2, + UniformSourceEllipticPde_3, + UniformSourceParabolicPde_2, + UniformSourceParabolicPde_3, + VolumeDependentAveragedSourceEllipticPde_2, + VolumeDependentAveragedSourceEllipticPde_3, +) +from chaste._syntax import DeprecatedClass, TemplateClassDict + +# Template Class Syntax +AveragedSourceEllipticPde = TemplateClassDict( + { + ("2",): AveragedSourceEllipticPde_2, + ("3",): AveragedSourceEllipticPde_3, + } +) + +AveragedSourceParabolicPde = TemplateClassDict( + { + ("2",): AveragedSourceParabolicPde_2, + ("3",): AveragedSourceParabolicPde_3, + } +) + +CellBasedEllipticPdeSolver = TemplateClassDict( + { + ("2",): CellBasedEllipticPdeSolver_2, + ("3",): CellBasedEllipticPdeSolver_3, + } +) + +CellBasedParabolicPdeSolver = TemplateClassDict( + { + ("2",): CellBasedParabolicPdeSolver_2, + ("3",): CellBasedParabolicPdeSolver_3, + } +) + +CellwiseSourceEllipticPde = TemplateClassDict( + { + ("2",): CellwiseSourceEllipticPde_2, + ("3",): CellwiseSourceEllipticPde_3, + } +) + +CellwiseSourceParabolicPde = TemplateClassDict( + { + ("2",): CellwiseSourceParabolicPde_2, + ("3",): CellwiseSourceParabolicPde_3, + } +) + +ConstBoundaryCondition = TemplateClassDict( + { + ("2",): ConstBoundaryCondition_2, + ("3",): ConstBoundaryCondition_3, + } +) + +EllipticBoxDomainPdeModifier = TemplateClassDict( + { + ("2",): EllipticBoxDomainPdeModifier_2, + ("3",): EllipticBoxDomainPdeModifier_3, + } +) + +EllipticGrowingDomainPdeModifier = TemplateClassDict( + { + ("2",): EllipticGrowingDomainPdeModifier_2, + ("3",): EllipticGrowingDomainPdeModifier_3, + } +) + +ParabolicBoxDomainPdeModifier = TemplateClassDict( + { + ("2",): ParabolicBoxDomainPdeModifier_2, + ("3",): ParabolicBoxDomainPdeModifier_3, + } +) + +ParabolicGrowingDomainPdeModifier = TemplateClassDict( + { + ("2",): ParabolicGrowingDomainPdeModifier_2, + ("3",): ParabolicGrowingDomainPdeModifier_3, + } +) + + +UniformSourceEllipticPde = TemplateClassDict( + { + ("2",): UniformSourceEllipticPde_2, + ("3",): UniformSourceEllipticPde_3, + } +) + +UniformSourceParabolicPde = TemplateClassDict( + { + ("2",): UniformSourceParabolicPde_2, + ("3",): UniformSourceParabolicPde_3, + } +) + +VolumeDependentAveragedSourceEllipticPde = TemplateClassDict( + { + ("2",): VolumeDependentAveragedSourceEllipticPde_2, + ("3",): VolumeDependentAveragedSourceEllipticPde_3, + } +) + +# Deprecated Class Syntax +AveragedSourceEllipticPde2 = DeprecatedClass("AveragedSourceEllipticPde2", AveragedSourceEllipticPde_2) +AveragedSourceEllipticPde3 = DeprecatedClass("AveragedSourceEllipticPde3", AveragedSourceEllipticPde_3) +AveragedSourceParabolicPde2 = DeprecatedClass("AveragedSourceParabolicPde2", AveragedSourceParabolicPde_2) +AveragedSourceParabolicPde3 = DeprecatedClass("AveragedSourceParabolicPde3", AveragedSourceParabolicPde_3) +CellBasedEllipticPdeSolver2 = DeprecatedClass("CellBasedEllipticPdeSolver2", CellBasedEllipticPdeSolver_2) +CellBasedEllipticPdeSolver3 = DeprecatedClass("CellBasedEllipticPdeSolver3", CellBasedEllipticPdeSolver_3) +CellBasedParabolicPdeSolver2 = DeprecatedClass("CellBasedParabolicPdeSolver2", CellBasedParabolicPdeSolver_2) +CellBasedParabolicPdeSolver3 = DeprecatedClass("CellBasedParabolicPdeSolver3", CellBasedParabolicPdeSolver_3) +CellwiseSourceEllipticPde2 = DeprecatedClass("CellwiseSourceEllipticPde2", CellwiseSourceEllipticPde_2) +CellwiseSourceEllipticPde3 = DeprecatedClass("CellwiseSourceEllipticPde3", CellwiseSourceEllipticPde_3) +CellwiseSourceParabolicPde2 = DeprecatedClass("CellwiseSourceParabolicPde2", CellwiseSourceParabolicPde_2) +CellwiseSourceParabolicPde3 = DeprecatedClass("CellwiseSourceParabolicPde3", CellwiseSourceParabolicPde_3) +ConstBoundaryCondition2 = DeprecatedClass("ConstBoundaryCondition2", ConstBoundaryCondition_2) +ConstBoundaryCondition3 = DeprecatedClass("ConstBoundaryCondition3", ConstBoundaryCondition_3) +EllipticBoxDomainPdeModifier2 = DeprecatedClass("EllipticBoxDomainPdeModifier2", EllipticBoxDomainPdeModifier_2) +EllipticBoxDomainPdeModifier3 = DeprecatedClass("EllipticBoxDomainPdeModifier3", EllipticBoxDomainPdeModifier_3) +EllipticGrowingDomainPdeModifier2 = DeprecatedClass("EllipticGrowingDomainPdeModifier2", EllipticGrowingDomainPdeModifier_2) +EllipticGrowingDomainPdeModifier3 = DeprecatedClass("EllipticGrowingDomainPdeModifier3", EllipticGrowingDomainPdeModifier_3) +ParabolicBoxDomainPdeModifier2 = DeprecatedClass("ParabolicBoxDomainPdeModifier2", ParabolicBoxDomainPdeModifier_2) +ParabolicBoxDomainPdeModifier3 = DeprecatedClass("ParabolicBoxDomainPdeModifier3", ParabolicBoxDomainPdeModifier_3) +ParabolicGrowingDomainPdeModifier2 = DeprecatedClass("ParabolicGrowingDomainPdeModifier2", ParabolicGrowingDomainPdeModifier_2) +ParabolicGrowingDomainPdeModifier3 = DeprecatedClass("ParabolicGrowingDomainPdeModifier3", ParabolicGrowingDomainPdeModifier_3) +UniformSourceEllipticPde2 = DeprecatedClass("UniformSourceEllipticPde2", UniformSourceEllipticPde_2) +UniformSourceEllipticPde3 = DeprecatedClass("UniformSourceEllipticPde3", UniformSourceEllipticPde_3) +UniformSourceParabolicPde2 = DeprecatedClass("UniformSourceParabolicPde2", UniformSourceParabolicPde_2) +UniformSourceParabolicPde3 = DeprecatedClass("UniformSourceParabolicPde3", UniformSourceParabolicPde_3) +VolumeDependentAveragedSourceEllipticPde2 = DeprecatedClass("VolumeDependentAveragedSourceEllipticPde2", VolumeDependentAveragedSourceEllipticPde_2) +VolumeDependentAveragedSourceEllipticPde3 = DeprecatedClass("VolumeDependentAveragedSourceEllipticPde3", VolumeDependentAveragedSourceEllipticPde_3) diff --git a/pychaste/src/py/chaste/visualization/__init__.py b/pychaste/src/py/chaste/visualization/__init__.py new file mode 100644 index 0000000000..d3a3d3534b --- /dev/null +++ b/pychaste/src/py/chaste/visualization/__init__.py @@ -0,0 +1,90 @@ +"""Visualization Module""" + +__copyright__ = """Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import importlib.util +import warnings + +from chaste._pychaste_all import ( + CellPopulationPyChasteActorGenerator_2, + CellPopulationPyChasteActorGenerator_3, + VtkScene_2, + VtkScene_3, +) +from chaste._syntax import DeprecatedClass, TemplateClassDict + +ipython_spec = importlib.util.find_spec("IPython") +if ipython_spec is None: + warnings.warn("IPython not found... skipping Jupyter imports.") + +else: + from chaste.visualization._jupyter import ( + JupyterNotebookManager, + JupyterSceneModifier_2, + JupyterSceneModifier_3, + ) + +# Template Class Syntax +CellPopulationPyChasteActorGenerator = TemplateClassDict( + { + ("2",): CellPopulationPyChasteActorGenerator_2, + ("3",): CellPopulationPyChasteActorGenerator_3, + } +) + +VtkScene = TemplateClassDict( + { + ("2",): VtkScene_2, + ("3",): VtkScene_3, + } +) + +if ipython_spec is not None: + JupyterSceneModifier = TemplateClassDict( + { + ("2",): JupyterSceneModifier_2, + ("3",): JupyterSceneModifier_3, + } + ) + +# Deprecated Class Syntax +CellPopulationPyChasteActorGenerator2 = DeprecatedClass("CellPopulationPyChasteActorGenerator2", CellPopulationPyChasteActorGenerator_2) +CellPopulationPyChasteActorGenerator3 = DeprecatedClass("CellPopulationPyChasteActorGenerator3", CellPopulationPyChasteActorGenerator_3) +VtkScene2 = DeprecatedClass("VtkScene2", VtkScene_2) +VtkScene3 = DeprecatedClass("VtkScene3", VtkScene_3) + +if ipython_spec is not None: + JupyterSceneModifier2 = DeprecatedClass("JupyterSceneModifier2", JupyterSceneModifier_2) + JupyterSceneModifier3 = DeprecatedClass("JupyterSceneModifier3", JupyterSceneModifier_3) + +del ipython_spec diff --git a/pychaste/src/py/chaste/visualization/_jupyter.py b/pychaste/src/py/chaste/visualization/_jupyter.py new file mode 100644 index 0000000000..3b173410a9 --- /dev/null +++ b/pychaste/src/py/chaste/visualization/_jupyter.py @@ -0,0 +1,209 @@ +"""Helper classes for visualization in Jupyter notebooks.""" + +__copyright__ = """Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +try: + import IPython +except ImportError as e: + raise ImportError("Cannot use Jupyter imports without Jupyter.") from e + +import os +import warnings +from io import StringIO + +import vtk +import xvfbwrapper +from pkg_resources import resource_filename + +from chaste.cell_based import VtkSceneModifier_2, VtkSceneModifier_3 + +# TODO: pkg_resources is deprecated in Python 3.12. + + +class JupyterNotebookManager: + """ + Singleton class for managing plotting in a Jupyter notebook + """ + + def __new__(cls, *args, **kwds): + """Singleton pattern""" + # https://www.python.org/download/releases/2.2/descrintro/#__new__ + it = cls.__dict__.get("__it__", None) + if it is not None: + return it + it = object.__new__(cls) + cls.__it__ = it + it._init(*args, **kwds) + return it + + def _init(self, *args, **kwds): + try: + self.vdisplay = xvfbwrapper.Xvfb() + self.vdisplay.start() + except OSError: + self.vdisplay = None + warnings.warn("Could not start Xvfb.") + + self.renderWindow = vtk.vtkRenderWindow() + + self.interactive_plotting_loaded = False + self.three_js_dir = resource_filename("chaste", os.path.join("external")) + self.container_id = 0 + + def _interactive_plot_init(self): + if not self.interactive_plotting_loaded: + + library_javascript = StringIO() + library_javascript.write( + """ + + """ + + IPython.display.display(IPython.display.HTML(html_source)) + + def vtk_show( + self, scene, width=400, height=300, output_format="png", increment=True + ): + """ + Takes vtkRenderer instance and returns an IPython Image with the rendering. + """ + + scene.ResetRenderer(0) + renderer = scene.GetRenderer() + + self.renderWindow.SetOffScreenRendering(1) + self.renderWindow.AddRenderer(renderer) + self.renderWindow.SetSize(width, height) + self.renderWindow.Render() + + if output_format == "wrl": + exporter = vtk.vtkVRMLExporter() + exporter.SetInput(self.renderWindow) + exporter.SetFileName(os.getcwd() + "/temp_scene.wrl") + exporter.Write() + self._interactive_plot_show(width, height, "temp_scene.wrl", increment) + self.renderWindow.RemoveRenderer(renderer) + + else: + windowToImageFilter = vtk.vtkWindowToImageFilter() + windowToImageFilter.SetInput(self.renderWindow) + windowToImageFilter.Update() + + writer = vtk.vtkPNGWriter() + writer.SetWriteToMemory(1) + writer.SetInputConnection(windowToImageFilter.GetOutputPort()) + writer.Write() + data = memoryview(writer.GetResult()) + self.renderWindow.RemoveRenderer(renderer) + + return IPython.display.Image(data) + + +def JupyterSceneModifierFactory(VtkSceneModifier): + + class JupyterSceneModifier(VtkSceneModifier): + """ + Class for real time plotting of output + """ + + def __init__(self, plotting_manager): + self.output_format = "png" + self.plotting_manager = plotting_manager + super().__init__() + + def UpdateAtEndOfTimeStep(self, cell_population): + """ + Update the Jupyter notebook plot with the new scene + """ + + super().UpdateAtEndOfTimeStep(cell_population) + + IPython.display.clear_output(wait=True) + + if self.output_format == "png": + IPython.display.display( + self.plotting_manager.vtk_show( + self.GetVtkScene(), output_format=self.output_format + ) + ) + else: + self.plotting_manager.vtk_show( + self.GetVtkScene(), output_format=self.output_format + ) + + return JupyterSceneModifier + + +JupyterSceneModifier_2 = JupyterSceneModifierFactory(VtkSceneModifier_2) +JupyterSceneModifier_3 = JupyterSceneModifierFactory(VtkSceneModifier_3) diff --git a/pychaste/src/py/conda/README.md b/pychaste/src/py/conda/README.md new file mode 100644 index 0000000000..3cc57c60d3 --- /dev/null +++ b/pychaste/src/py/conda/README.md @@ -0,0 +1,82 @@ +# PyChaste conda packages + +The conda [pychaste](https://anaconda.org/pychaste) channel hosts the `chaste` and `xsd` conda packages. All other dependencies are installed from the [conda-forge](https://anaconda.org/conda-forge) channel. + +## How to build the conda packages + +This directory contains scripts for building the `chaste` conda package and uploading it to the conda [pychaste](https://anaconda.org/pychaste) channel. + +The following instructions assume that [mamba](https://mamba.readthedocs.io) and [docker](https://docs.docker.com/get-docker/) have already been installed. + +Launch a docker container to build the package: + +```bash +cd /path/to/Chaste/pychaste/src/py/conda + +docker run -it --rm \ + -v $(pwd):/home/conda \ + -e HOST_USER_ID="$(id -u)" \ + quay.io/condaforge/linux-anvil-cos7-x86_64 \ + ./build-package.sh --variant= --branch= --parallel= +``` + +The `--variant` argument specifies a build variant name. A list of variant files can be found in the `variants` subdirectory. The variant name is the name of the file without the `.yaml` extenstion. + +The optional `--build` argument specifies the Chaste branch/tag to build from; the default is `develop`. + +The optional `--parallel` argument specifies the maximum number of parallel build processes. + +After the build is complete, verify that the package has been created: + +```bash +ls ./build_artifacts/linux-64 +``` + +There should be a `--.tar.bz2` file in the directory. + +Test the newly built package by installing it in a conda environment: + +```bash +conda create -n py38-env python=3.8 +conda activate py38-env +conda install -c ./build_artifacts --.tar.bz2 +``` + +Run tests on the package: + +```bash +python -m unittest discover /path/to/Chaste/pychaste/test +``` + +To upload the package to the conda [pychaste](https://anaconda.org/pychaste) channel, first install `anaconda-client`: + +```bash +conda install anaconda-client +``` + +Login and upload the package: + +```bash +anaconda login +anaconda upload -u 'pychaste' ./build_artifacts/--.tar.bz2 +``` + +## Directory structure + +The conda scripts directory has this structure: + +``` +├── build-package.sh +├── recipe +│   ├── build.sh +│   └── meta.yaml +└── variants + ├── .yaml + ├── .yaml + └── .yaml +``` + +- `build-package.sh`: This sets up the environment for the build and runs `conda mambabuild` to create the package. +- `recipe/build.sh`: This script is used by `conda mambabuild` to build the source code. Typically, this performs the "configure" and "make" steps. See the [conda-build script documentation](https://docs.conda.io/projects/conda-build/en/latest/resources/build-scripts.html) for more information. +- `recipe/meta.yaml`: This contains metadata used by `conda mambabuild` such as build dependencies. For more information, see the [conda-build metadata documentation](https://docs.conda.io/projects/conda-build/en/latest/resources/define-metadata.html). +- `variants`: This contains additional metadata for different variants of the build, which are added on top of the metadata provided in `meta.yaml`. Typically, a variant file adds specific versions of dependencies required for that variant. For further information, see the [conda-build variant documentation](https://docs.conda.io/projects/conda-build/en/stable/resources/variants.html). diff --git a/pychaste/src/py/conda/build-package.sh b/pychaste/src/py/conda/build-package.sh new file mode 100755 index 0000000000..ae2ff40999 --- /dev/null +++ b/pychaste/src/py/conda/build-package.sh @@ -0,0 +1,98 @@ +#!/bin/bash -e + +# Example usage: +# ./build-package.sh --variant=linux_64_python3.8_cpython --branch=2024.1 --parallel=4 +# +# Intended for use in a build container e.g.: +# docker run --rm -it quay.io/condaforge/linux-anvil-cos7-x86_64 /bin/bash + +# Parse args +variant= +branch= +parallel= + +for option; do + case $option in + --variant=*) + variant=$(expr "x$option" : "x--variant=\(.*\)") + ;; + --branch=*) + branch=$(expr "x$option" : "x--branch=\(.*\)") + ;; + --parallel=*) + parallel=$(expr "x$option" : "x--parallel=\(.*\)") + ;; + *) + echo "Unknown option: $option" 1>&2 + exit 1 + ;; + esac +done + +if [ -z "${variant}" ]; then usage; fi +if [ -z "${branch}" ]; then branch=develop; fi + +set -x + +# Configure environment +export FEEDSTOCK_ROOT="$(pwd)" +export RECIPE_ROOT="${FEEDSTOCK_ROOT}/recipe" +export CONFIG_FILE="${FEEDSTOCK_ROOT}/variants/${variant}.yaml" +export CONDA_BLD_PATH="${FEEDSTOCK_ROOT}/build_artifacts" + +export CPU_COUNT="${parallel:-$(nproc)}" + +export PYTHONUNBUFFERED=1 + +# Configure conda build path +mkdir -p "${CONDA_BLD_PATH}" + +cat >~/.condarc <"${CONDA_PREFIX}"/etc/conda/activate.d/conda-forge-ci-setup-activate.sh < + Chaste is a general purpose simulation package for computational biology. + dev_url: https://github.com/Chaste/Chaste/ + doc_url: https://chaste.github.io/docs/ diff --git a/pychaste/src/py/conda/variants/linux_64_python3.10_cpython.yaml b/pychaste/src/py/conda/variants/linux_64_python3.10_cpython.yaml new file mode 100755 index 0000000000..e8f0221259 --- /dev/null +++ b/pychaste/src/py/conda/variants/linux_64_python3.10_cpython.yaml @@ -0,0 +1,28 @@ +cdt_name: + - cos7 +channel_sources: + - conda-forge + - pychaste +channel_targets: + - pychaste main +cxx_compiler: + - gxx +cxx_compiler_version: + - "11" +docker_image: + - quay.io/condaforge/linux-anvil-cos7-x86_64 +fortran_compiler: + - gfortran +fortran_compiler_version: + - "11" +pin_run_as_build: + python: + min_pin: x.x + max_pin: x.x +python: + - 3.10.* *_cpython +target_platform: + - linux-64 +zip_keys: + - - cxx_compiler_version + - fortran_compiler_version diff --git a/pychaste/src/py/conda/variants/linux_64_python3.11_cpython.yaml b/pychaste/src/py/conda/variants/linux_64_python3.11_cpython.yaml new file mode 100755 index 0000000000..e700d7905f --- /dev/null +++ b/pychaste/src/py/conda/variants/linux_64_python3.11_cpython.yaml @@ -0,0 +1,28 @@ +cdt_name: + - cos7 +channel_sources: + - conda-forge + - pychaste +channel_targets: + - pychaste main +cxx_compiler: + - gxx +cxx_compiler_version: + - "11" +docker_image: + - quay.io/condaforge/linux-anvil-cos7-x86_64 +fortran_compiler: + - gfortran +fortran_compiler_version: + - "11" +pin_run_as_build: + python: + min_pin: x.x + max_pin: x.x +python: + - 3.11.* *_cpython +target_platform: + - linux-64 +zip_keys: + - - cxx_compiler_version + - fortran_compiler_version diff --git a/pychaste/src/py/conda/variants/linux_64_python3.12_cpython.yaml b/pychaste/src/py/conda/variants/linux_64_python3.12_cpython.yaml new file mode 100755 index 0000000000..5997fac022 --- /dev/null +++ b/pychaste/src/py/conda/variants/linux_64_python3.12_cpython.yaml @@ -0,0 +1,28 @@ +cdt_name: + - cos7 +channel_sources: + - conda-forge + - pychaste +channel_targets: + - pychaste main +cxx_compiler: + - gxx +cxx_compiler_version: + - "11" +docker_image: + - quay.io/condaforge/linux-anvil-cos7-x86_64 +fortran_compiler: + - gfortran +fortran_compiler_version: + - "11" +pin_run_as_build: + python: + min_pin: x.x + max_pin: x.x +python: + - 3.12.* *_cpython +target_platform: + - linux-64 +zip_keys: + - - cxx_compiler_version + - fortran_compiler_version diff --git a/pychaste/src/py/conda/variants/linux_64_python3.8_cpython.yaml b/pychaste/src/py/conda/variants/linux_64_python3.8_cpython.yaml new file mode 100755 index 0000000000..224340e9a0 --- /dev/null +++ b/pychaste/src/py/conda/variants/linux_64_python3.8_cpython.yaml @@ -0,0 +1,28 @@ +cdt_name: + - cos7 +channel_sources: + - conda-forge + - pychaste +channel_targets: + - pychaste main +cxx_compiler: + - gxx +cxx_compiler_version: + - "11" +docker_image: + - quay.io/condaforge/linux-anvil-cos7-x86_64 +fortran_compiler: + - gfortran +fortran_compiler_version: + - "11" +pin_run_as_build: + python: + min_pin: x.x + max_pin: x.x +python: + - 3.8.* *_cpython +target_platform: + - linux-64 +zip_keys: + - - cxx_compiler_version + - fortran_compiler_version diff --git a/pychaste/src/py/conda/variants/linux_64_python3.9_cpython.yaml b/pychaste/src/py/conda/variants/linux_64_python3.9_cpython.yaml new file mode 100755 index 0000000000..ea1db2d11f --- /dev/null +++ b/pychaste/src/py/conda/variants/linux_64_python3.9_cpython.yaml @@ -0,0 +1,28 @@ +cdt_name: + - cos7 +channel_sources: + - conda-forge + - pychaste +channel_targets: + - pychaste main +cxx_compiler: + - gxx +cxx_compiler_version: + - "11" +docker_image: + - quay.io/condaforge/linux-anvil-cos7-x86_64 +fortran_compiler: + - gfortran +fortran_compiler_version: + - "11" +pin_run_as_build: + python: + min_pin: x.x + max_pin: x.x +python: + - 3.9.* *_cpython +target_platform: + - linux-64 +zip_keys: + - - cxx_compiler_version + - fortran_compiler_version diff --git a/pychaste/src/py/doc/api/Makefile b/pychaste/src/py/doc/api/Makefile new file mode 100644 index 0000000000..6e345c60b2 --- /dev/null +++ b/pychaste/src/py/doc/api/Makefile @@ -0,0 +1,216 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source + +.PHONY: help +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + +.PHONY: clean +clean: + rm -rf $(BUILDDIR)/* + +.PHONY: html +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +.PHONY: dirhtml +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +.PHONY: singlehtml +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +.PHONY: pickle +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +.PHONY: json +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +.PHONY: htmlhelp +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +.PHONY: qthelp +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/chaste.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/chaste.qhc" + +.PHONY: applehelp +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +.PHONY: devhelp +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/chaste" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/chaste" + @echo "# devhelp" + +.PHONY: epub +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +.PHONY: latex +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +.PHONY: latexpdf +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: latexpdfja +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: text +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +.PHONY: man +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +.PHONY: texinfo +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +.PHONY: info +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +.PHONY: gettext +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +.PHONY: changes +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +.PHONY: linkcheck +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +.PHONY: doctest +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +.PHONY: coverage +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +.PHONY: xml +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +.PHONY: pseudoxml +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/pychaste/src/py/doc/api/source/chaste.cell_based.rst b/pychaste/src/py/doc/api/source/chaste.cell_based.rst new file mode 100644 index 0000000000..4765c18188 --- /dev/null +++ b/pychaste/src/py/doc/api/source/chaste.cell_based.rst @@ -0,0 +1,380 @@ +chaste.cell_based package +========================= + +Module contents +--------------- + +.. automodule:: chaste.cell_based + :members: + :undoc-members: + :show-inheritance: + +.. autoclass:: chaste.cell_based.Identifiable + :members: + +.. autoclass:: chaste.cell_based.Cell + :members: + +.. autoclass:: chaste.cell_based.CellPropertyCollection + :members: + +.. autoclass:: chaste.cell_based.CellId + :members: + +.. autoclass:: chaste.cell_based.CellLabel + :members: + +.. autoclass:: chaste.cell_based.CellData + :members: + +.. autoclass:: chaste.cell_based.CellAncestor + :members: + +.. autoclass:: chaste.cell_based.CellPropertyRegistry + :members: + +.. autoclass:: chaste.cell_based.SimulationTime + :members: + +.. autoclass:: chaste.cell_based.OnLatticeSimulation2 + :members: + +.. autoclass:: chaste.cell_based.OnLatticeSimulation3 + :members: + +.. autoclass:: chaste.cell_based.DiffusionCaUpdateRule2 + :members: + +.. autoclass:: chaste.cell_based.DiffusionCaUpdateRule3 + :members: + +.. autoclass:: chaste.cell_based.AbstractCaUpdateRule2 + :members: + +.. autoclass:: chaste.cell_based.AbstractCaUpdateRule3 + :members: + +.. autoclass:: chaste.cell_based.AbstractUpdateRule2 + :members: + +.. autoclass:: chaste.cell_based.AbstractUpdateRule3 + :members: + +.. autoclass:: chaste.cell_based.VtkSceneModifier2 + :members: + +.. autoclass:: chaste.cell_based.VtkSceneModifier3 + :members: + +.. autoclass:: chaste.cell_based.AbstractTargetAreaModifier2 + :members: + +.. autoclass:: chaste.cell_based.AbstractTargetAreaModifier3 + :members: + +.. autoclass:: chaste.cell_based.SimpleTargetAreaModifier2 + :members: + +.. autoclass:: chaste.cell_based.SimpleTargetAreaModifier3 + :members: + +.. autoclass:: chaste.cell_based.NagaiHondaForce2 + :members: + +.. autoclass:: chaste.cell_based.NagaiHondaForce3 + :members: + +.. autoclass:: chaste.cell_based.AbstractPottsUpdateRule2 + :members: + +.. autoclass:: chaste.cell_based.AbstractPottsUpdateRule3 + :members: + +.. autoclass:: chaste.cell_based.VolumeConstraintPottsUpdateRule2 + :members: + +.. autoclass:: chaste.cell_based.VolumeConstraintPottsUpdateRule3 + :members: + +.. autoclass:: chaste.cell_based.SurfaceAreaConstraintPottsUpdateRule2 + :members: + +.. autoclass:: chaste.cell_based.SurfaceAreaConstraintPottsUpdateRule3 + :members: + +.. autoclass:: chaste.cell_based.DifferentialAdhesionPottsUpdateRule2 + :members: + +.. autoclass:: chaste.cell_based.DifferentialAdhesionPottsUpdateRule3 + :members: + +.. autoclass:: chaste.cell_based.AdhesionPottsUpdateRule2 + :members: + +.. autoclass:: chaste.cell_based.AdhesionPottsUpdateRule3 + :members: + +.. autoclass:: chaste.cell_based.SphereGeometryBoundaryCondition2 + :members: + +.. autoclass:: chaste.cell_based.SphereGeometryBoundaryCondition3 + :members: + +.. autoclass:: chaste.cell_based.AbstractCellKiller2 + :members: + +.. autoclass:: chaste.cell_based.AbstractCellKiller3 + :members: + +.. autoclass:: chaste.cell_based.PlaneBasedCellKiller2 + :members: + +.. autoclass:: chaste.cell_based.PlaneBasedCellKiller3 + :members: + +.. autoclass:: chaste.cell_based.ApoptoticCellKiller2 + :members: + +.. autoclass:: chaste.cell_based.ApoptoticCellKiller3 + :members: + +.. autoclass:: chaste.cell_based.RandomCellKiller2 + :members: + +.. autoclass:: chaste.cell_based.RandomCellKiller3 + :members: + +.. autoclass:: chaste.cell_based.CellwiseSourceEllipticPde2 + :members: + +.. autoclass:: chaste.cell_based.CellwiseSourceEllipticPde3 + :members: + +.. autoclass:: chaste.cell_based.AbstractPdeModifier2 + :members: + +.. autoclass:: chaste.cell_based.AbstractPdeModifier3 + :members: + +.. autoclass:: chaste.cell_based.AbstractGrowingDomainPdeModifier2 + :members: + +.. autoclass:: chaste.cell_based.AbstractGrowingDomainPdeModifier3 + :members: + +.. autoclass:: chaste.cell_based.EllipticGrowingDomainPdeModifier2 + :members: + +.. autoclass:: chaste.cell_based.EllipticGrowingDomainPdeModifier3 + :members: + +.. autoclass:: chaste.cell_based.AbstractCellBasedSimulation2_2 + :members: + +.. autoclass:: chaste.cell_based.AbstractCellBasedSimulation3_3 + :members: + +.. autoclass:: chaste.cell_based.OffLatticeSimulation2_2 + :members: + +.. autoclass:: chaste.cell_based.OffLatticeSimulation3_3 + :members: + +.. autoclass:: chaste.cell_based.AbstractCellBasedSimulationModifier2_2 + :members: + +.. autoclass:: chaste.cell_based.AbstractCellBasedSimulationModifier3_3 + :members: + +.. autoclass:: chaste.cell_based.AbstractTwoBodyInteractionForce2_2 + :members: + +.. autoclass:: chaste.cell_based.AbstractTwoBodyInteractionForce3_3 + :members: + +.. autoclass:: chaste.cell_based.AbstractForce2_2 + :members: + +.. autoclass:: chaste.cell_based.AbstractForce3_3 + :members: + +.. autoclass:: chaste.cell_based.GeneralisedLinearSpringForce2_2 + :members: + +.. autoclass:: chaste.cell_based.GeneralisedLinearSpringForce3_3 + :members: + +.. autoclass:: chaste.cell_based.AbstractCellPopulationBoundaryCondition2_2 + :members: + +.. autoclass:: chaste.cell_based.AbstractCellPopulationBoundaryCondition3_3 + :members: + +.. autoclass:: chaste.cell_based.PlaneBoundaryCondition2_2 + :members: + +.. autoclass:: chaste.cell_based.PlaneBoundaryCondition3_3 + :members: + +.. autoclass:: chaste.cell_based.AttractingPlaneBoundaryCondition2_2 + :members: + +.. autoclass:: chaste.cell_based.AttractingPlaneBoundaryCondition3_3 + :members: + +.. autoclass:: chaste.cell_based.AbstractLinearEllipticPde2_2 + :members: + +.. autoclass:: chaste.cell_based.AbstractLinearEllipticPde3_3 + :members: + +.. autoclass:: chaste.cell_based.AbstractLinearPde2_2 + :members: + +.. autoclass:: chaste.cell_based.AbstractLinearPde3_3 + :members: + +.. autoclass:: chaste.cell_based.AbstractCellProperty + :members: + +.. autoclass:: chaste.cell_based.AbstractCellProliferativeType + :members: + +.. autoclass:: chaste.cell_based.StemCellProliferativeType + :members: + +.. autoclass:: chaste.cell_based.DefaultCellProliferativeType + :members: + +.. autoclass:: chaste.cell_based.TransitCellProliferativeType + :members: + +.. autoclass:: chaste.cell_based.DifferentiatedCellProliferativeType + :members: + +.. autoclass:: chaste.cell_based.AbstractCellMutationState + :members: + +.. autoclass:: chaste.cell_based.ApcOneHitCellMutationState + :members: + +.. autoclass:: chaste.cell_based.ApcTwoHitCellMutationState + :members: + +.. autoclass:: chaste.cell_based.BetaCateninOneHitCellMutationState + :members: + +.. autoclass:: chaste.cell_based.WildTypeCellMutationState + :members: + +.. autoclass:: chaste.cell_based.AbstractCellCycleModel + :members: + +.. autoclass:: chaste.cell_based.AbstractPhaseBasedCellCycleModel + :members: + +.. autoclass:: chaste.cell_based.AbstractSimpleCellCycleModel + :members: + +.. autoclass:: chaste.cell_based.AbstractSimplePhaseBasedCellCycleModel + :members: + +.. autoclass:: chaste.cell_based.AbstractSimpleGenerationalCellCycleModel + :members: + +.. autoclass:: chaste.cell_based.UniformCellCycleModel + :members: + +.. autoclass:: chaste.cell_based.SimpleOxygenBasedCellCycleModel + :members: + +.. autoclass:: chaste.cell_based.UniformG1GenerationalCellCycleModel + :members: + +.. autoclass:: chaste.cell_based.NoCellCycleModel + :members: + +.. autoclass:: chaste.cell_based.CellsGenerator + :members: + +.. autoclass:: chaste.cell_based.CellsGenerator + :members: + +.. autoclass:: chaste.cell_based.CellsGenerator + :members: + +.. autoclass:: chaste.cell_based.CellsGenerator + :members: + +.. autoclass:: chaste.cell_based.CellsGenerator + :members: + +.. autoclass:: chaste.cell_based.CellsGenerator + :members: + +.. autoclass:: chaste.cell_based.CellsGenerator + :members: + +.. autoclass:: chaste.cell_based.CellsGenerator + :members: + +.. autoclass:: chaste.cell_based.AbstractOnLatticeCellPopulation<2> + :members: + +.. autoclass:: chaste.cell_based.AbstractOnLatticeCellPopulation<3> + :members: + +.. autoclass:: chaste.cell_based.AbstractCellPopulation<2,2> + :members: + +.. autoclass:: chaste.cell_based.AbstractCellPopulation<3,3> + :members: + +.. autoclass:: chaste.cell_based.AbstractCentreBasedCellPopulation<2,2> + :members: + +.. autoclass:: chaste.cell_based.AbstractCentreBasedCellPopulation<3,3> + :members: + +.. autoclass:: chaste.cell_based.AbstractOffLatticeCellPopulation<2,2> + :members: + +.. autoclass:: chaste.cell_based.AbstractOffLatticeCellPopulation<3,3> + :members: + +.. autoclass:: chaste.cell_based.NodeBasedCellPopulation<2> + :members: + +.. autoclass:: chaste.cell_based.NodeBasedCellPopulation<3> + :members: + +.. autoclass:: chaste.cell_based.CaBasedCellPopulation<2> + :members: + +.. autoclass:: chaste.cell_based.CaBasedCellPopulation<3> + :members: + +.. autoclass:: chaste.cell_based.VertexBasedCellPopulation<2> + :members: + +.. autoclass:: chaste.cell_based.VertexBasedCellPopulation<3> + :members: + +.. autoclass:: chaste.cell_based.PottsBasedCellPopulation<2> + :members: + +.. autoclass:: chaste.cell_based.PottsBasedCellPopulation<3> + :members: + +.. autoclass:: chaste.cell_based.MeshBasedCellPopulationWithGhostNodes<2> + :members: + +.. autoclass:: chaste.cell_based.MeshBasedCellPopulationWithGhostNodes<3> + :members: + +.. autoclass:: chaste.cell_based.MeshBasedCellPopulation<2,2> + :members: + +.. autoclass:: chaste.cell_based.MeshBasedCellPopulation<3,3> + :members: + diff --git a/pychaste/src/py/doc/api/source/chaste.core.rst b/pychaste/src/py/doc/api/source/chaste.core.rst new file mode 100644 index 0000000000..f022ebdd7f --- /dev/null +++ b/pychaste/src/py/doc/api/source/chaste.core.rst @@ -0,0 +1,35 @@ +chaste.core package +=================== + +Module contents +--------------- + +.. automodule:: chaste.core + :members: + :undoc-members: + :show-inheritance: + +.. autoclass:: chaste.core.OutputFileHandler + :members: + +.. autoclass:: chaste.core.RelativeTo + :members: + +.. autoclass:: chaste.core.FileFinder + :members: + +.. autoclass:: chaste.core.RandomNumberGenerator + :members: + +.. autoclass:: chaste.core.Timer + :members: + +.. autoclass:: chaste.core.ChasteBuildInfo + :members: + +.. autoclass:: chaste.core.PetscTools + :members: + +.. autoclass:: chaste.core.ReplicatableVector + :members: + diff --git a/pychaste/src/py/doc/api/source/chaste.mesh.rst b/pychaste/src/py/doc/api/source/chaste.mesh.rst new file mode 100644 index 0000000000..d9a1870eac --- /dev/null +++ b/pychaste/src/py/doc/api/source/chaste.mesh.rst @@ -0,0 +1,89 @@ +chaste.mesh package +=================== + +Module contents +--------------- + +.. automodule:: chaste.mesh + :members: + :undoc-members: + :show-inheritance: + +.. autoclass:: chaste.mesh.ChastePoint3 + :members: + +.. autoclass:: chaste.mesh.Node3 + :members: + +.. autoclass:: chaste.mesh.NodeAttributes3 + :members: + +.. autoclass:: chaste.mesh.PottsMesh3 + :members: + +.. autoclass:: chaste.mesh.SharedPottsMeshGenerator3 + :members: + +.. autoclass:: chaste.mesh.ChastePoint2 + :members: + +.. autoclass:: chaste.mesh.Node2 + :members: + +.. autoclass:: chaste.mesh.NodeAttributes2 + :members: + +.. autoclass:: chaste.mesh.PottsMesh2 + :members: + +.. autoclass:: chaste.mesh.SharedPottsMeshGenerator2 + :members: + +.. autoclass:: chaste.mesh.NodesOnlyMesh2 + :members: + +.. autoclass:: chaste.mesh.NodesOnlyMesh3 + :members: + +.. autoclass:: chaste.mesh.MutableMesh2_2 + :members: + +.. autoclass:: chaste.mesh.MutableMesh3_3 + :members: + +.. autoclass:: chaste.mesh.TetrahedralMesh2_2 + :members: + +.. autoclass:: chaste.mesh.TetrahedralMesh3_3 + :members: + +.. autoclass:: chaste.mesh.AbstractTetrahedralMesh2_2 + :members: + +.. autoclass:: chaste.mesh.AbstractTetrahedralMesh3_3 + :members: + +.. autoclass:: chaste.mesh.AbstractMesh2_2 + :members: + +.. autoclass:: chaste.mesh.AbstractMesh3_3 + :members: + +.. autoclass:: chaste.mesh.VertexMesh2_2 + :members: + +.. autoclass:: chaste.mesh.VertexMesh3_3 + :members: + +.. autoclass:: chaste.mesh.MutableVertexMesh2_2 + :members: + +.. autoclass:: chaste.mesh.MutableVertexMesh3_3 + :members: + +.. autoclass:: chaste.mesh.Cylindrical2dVertexMesh + :members: + +.. autoclass:: chaste.mesh.SharedCylindricalHoneycombVertexMeshGenerator + :members: + diff --git a/pychaste/src/py/doc/api/source/chaste.ode.rst b/pychaste/src/py/doc/api/source/chaste.ode.rst new file mode 100644 index 0000000000..cc303d48a2 --- /dev/null +++ b/pychaste/src/py/doc/api/source/chaste.ode.rst @@ -0,0 +1,21 @@ +chaste.ode package +================== + +Module contents +--------------- + +.. automodule:: chaste.ode + :members: + :undoc-members: + :show-inheritance: + +.. autoclass:: chaste.ode.AbstractOdeSystem + :members: + +.. autoclass:: chaste.ode.AbstractOdeSystemInformation + :members: + +.. autoclass:: chaste.ode.AbstractPythonOdeSystemInformation + :members: + + diff --git a/pychaste/src/py/doc/api/source/chaste.pde.rst b/pychaste/src/py/doc/api/source/chaste.pde.rst new file mode 100644 index 0000000000..f04d056a94 --- /dev/null +++ b/pychaste/src/py/doc/api/source/chaste.pde.rst @@ -0,0 +1,35 @@ +chaste.pde package +================== + +Module contents +--------------- + +.. automodule:: chaste.pde + :members: + :undoc-members: + :show-inheritance: + +.. autoclass:: chaste.pde.AbstractLinearPde3_3 + :members: + +.. autoclass:: chaste.pde.AbstractLinearPde2_2 + :members: + +.. autoclass:: chaste.pde.AbstractLinearEllipticPde3_3 + :members: + +.. autoclass:: chaste.pde.AbstractLinearEllipticPde2_2 + :members: + +.. autoclass:: chaste.pde.AbstractBoundaryCondition2 + :members: + +.. autoclass:: chaste.pde.AbstractBoundaryCondition3 + :members: + +.. autoclass:: chaste.pde.ConstBoundaryCondition2 + :members: + +.. autoclass:: chaste.pde.ConstBoundaryCondition3 + :members: + diff --git a/pychaste/src/py/doc/api/source/chaste.rst b/pychaste/src/py/doc/api/source/chaste.rst new file mode 100644 index 0000000000..37be87c63c --- /dev/null +++ b/pychaste/src/py/doc/api/source/chaste.rst @@ -0,0 +1,23 @@ +chaste package +============== + +Subpackages +----------- + +.. toctree:: + + chaste.cell_based + chaste.core + chaste.mesh + chaste.ode + chaste.pde + chaste.tutorial + chaste.visualization + +Module contents +--------------- + +.. automodule:: chaste + :members: + :undoc-members: + :show-inheritance: diff --git a/pychaste/src/py/doc/api/source/chaste.tutorial.rst b/pychaste/src/py/doc/api/source/chaste.tutorial.rst new file mode 100644 index 0000000000..22666e7491 --- /dev/null +++ b/pychaste/src/py/doc/api/source/chaste.tutorial.rst @@ -0,0 +1,14 @@ +chaste.tutorial package +======================= + +Module contents +--------------- + +.. automodule:: chaste.tutorial + :members: + :undoc-members: + :show-inheritance: + +.. autoclass:: chaste.tutorial.Hello + :members: + \ No newline at end of file diff --git a/pychaste/src/py/doc/api/source/chaste.visualization.rst b/pychaste/src/py/doc/api/source/chaste.visualization.rst new file mode 100644 index 0000000000..13e88e3367 --- /dev/null +++ b/pychaste/src/py/doc/api/source/chaste.visualization.rst @@ -0,0 +1,42 @@ +chaste.visualization package +============================ + +Submodules +---------- + +chaste.visualization.fortests module +------------------------------------ + +.. automodule:: chaste.visualization.fortests + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: chaste.visualization + :members: + :undoc-members: + :show-inheritance: + +.. autoclass:: chaste.visualization.VtkScene3 + :members: + +.. autoclass:: chaste.visualization.VtkScene2 + :members: + +.. autoclass:: chaste.visualization.AbstractPyChasteActorGenerator2 + :members: + +.. autoclass:: chaste.visualization.AbstractPyChasteActorGenerator3 + :members: + +.. autoclass:: chaste.visualization.CellPopulationPyChasteActorGenerator2 + :members: + +.. autoclass:: chaste.visualization.CellPopulationPyChasteActorGenerator3 + :members: + + diff --git a/pychaste/src/py/doc/api/source/conf.py b/pychaste/src/py/doc/api/source/conf.py new file mode 100644 index 0000000000..9fbd691a45 --- /dev/null +++ b/pychaste/src/py/doc/api/source/conf.py @@ -0,0 +1,293 @@ +# -*- coding: utf-8 -*- +# +# chaste documentation build configuration file, created by +# sphinx-quickstart on Tue Nov 22 08:46:29 2016. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os + +import sphinx_rtd_theme + +html_theme = "sphinx_rtd_theme" + +html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + +# 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('../../src/python/')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# 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.mathjax', + 'sphinx.ext.viewcode', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'chaste' +copyright = u'2024, University of Oxford' +author = u'University of Oxford' + +# 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. +version = u'3.4' +# The full version, including alpha/beta/rc tags. +release = u'3.4dev' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +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 = ['*setup.py', '*fortests*'] + +# 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. +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 + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = 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_theme = 'alabaster' + +# 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 +# " v 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 = None + +# The name of an image file (relative to this directory) to use as a favicon of +# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# 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'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# 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 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 + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' +#html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +#html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +#html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'chastedoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', + +# Latex figure (float) alignment +#'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'chaste.tex', u'chaste Documentation', + u'University of Oxford', '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 = [ + (master_doc, 'chaste', u'chaste Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for 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 = [ + (master_doc, 'chaste', u'chaste Documentation', + author, 'chaste', 'One line description of project.', + '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 diff --git a/pychaste/src/py/doc/api/source/index.rst b/pychaste/src/py/doc/api/source/index.rst new file mode 100644 index 0000000000..614c589710 --- /dev/null +++ b/pychaste/src/py/doc/api/source/index.rst @@ -0,0 +1,21 @@ +.. chaste documentation master file, created by + sphinx-quickstart on Tue Nov 22 08:46:29 2016. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +PyChaste API documentation +========================== + +Contents: + +.. toctree:: + :maxdepth: 3 + + chaste + +Indices and tables +================== +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/pychaste/src/py/doc/api/source/modules.rst b/pychaste/src/py/doc/api/source/modules.rst new file mode 100644 index 0000000000..9eae231c6b --- /dev/null +++ b/pychaste/src/py/doc/api/source/modules.rst @@ -0,0 +1,7 @@ +python +====== + +.. toctree:: + :maxdepth: 4 + + chaste diff --git a/pychaste/src/py/doc/tutorial/CellSorting.ipynb b/pychaste/src/py/doc/tutorial/CellSorting.ipynb new file mode 100644 index 0000000000..6258e3d63a --- /dev/null +++ b/pychaste/src/py/doc/tutorial/CellSorting.ipynb @@ -0,0 +1,310 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "dc9cd996", + "metadata": {}, + "source": [ + "This tutorial is automatically generated from [TestPyCellSortingTutorial.py](https://github.com/Chaste/Chaste/blob/develop/pychaste/test/tutorial/TestPyCellSortingTutorial.py) at revision [4045f91a83f5](https://github.com/Chaste/Chaste/commit/4045f91a83f55dc4a97f2ca4f97b0c32f4e43a4a)." + ] + }, + { + "cell_type": "markdown", + "id": "eab9f99c", + "metadata": {}, + "source": [ + "\n", + "## Introduction\n", + "This test is a demonstration of cell sorting using a Cellular Potts based framework.\n", + "It shows:\n", + " * How to set up a Potts simulation\n", + " * Working with labels\n", + " \n", + "## The Test\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "210aab0c", + "metadata": {}, + "outputs": [], + "source": [ + "import chaste # The PyChaste module\n", + "import chaste.cell_based # Contains cell populations\n", + "import chaste.mesh # Contains meshes\n", + "import chaste.visualization # Visualization tools\n", + "import matplotlib.pyplot as plt # Plotting\n", + "import numpy as np # Matrix tools" + ] + }, + { + "cell_type": "markdown", + "id": "48ee6cfa", + "metadata": {}, + "source": [ + "### Test 1 - Cell sorting\n", + "The next test generates a collection of cells, there are two types of cells, labelled ones and non labelled ones,\n", + "there is differential adhesion between the cell types. For the parameters specified, the cells sort into separate types.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "709128f2", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.cell_based.SetupNotebookTest() # Set up the test" + ] + }, + { + "cell_type": "markdown", + "id": "4aa9f2d3", + "metadata": {}, + "source": [ + "First, we generate a `Potts` mesh. To create a `PottsMesh`, we can use the `PottsMeshGenerator`.\n", + "This generates a regular square-shaped mesh, in which all elements are the same size.\n", + "We have chosen an 8 by 8 block of elements each consisting of 4 by 4 ( = 16) lattice sites.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b57aa8b6", + "metadata": {}, + "outputs": [], + "source": [ + "generator = chaste.mesh.PottsMeshGenerator[2](50, 8, 4, 50, 8, 4)\n", + "mesh = generator.GetMesh()" + ] + }, + { + "cell_type": "markdown", + "id": "a0b3c230", + "metadata": {}, + "source": [ + "Having created a mesh, we now create some cells. To do this, we the `CellsGenerator` helper class,\n", + "as before but this time the third argument is set to make all cells non-proliferative.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3eef4583", + "metadata": {}, + "outputs": [], + "source": [ + "differentiated_type = chaste.cell_based.DifferentiatedCellProliferativeType()\n", + "cell_generator = chaste.cell_based.CellsGenerator[\"UniformCellCycleModel\", 2]()\n", + "cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), differentiated_type)" + ] + }, + { + "cell_type": "markdown", + "id": "e643ffc4", + "metadata": {}, + "source": [ + "Before we make a CellPopulation we make a cell label and then assign this label to some randomly chosen cells.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "71235d48", + "metadata": {}, + "outputs": [], + "source": [ + "label = chaste.cell_based.CellLabel()\n", + "for eachCell in cells:\n", + " if chaste.core.RandomNumberGenerator.Instance().ranf() < 0.5:\n", + " eachCell.AddCellProperty(label)" + ] + }, + { + "cell_type": "markdown", + "id": "ae5d2be1", + "metadata": {}, + "source": [ + "Now we have a mesh and a set of cells to go with it, we can create a `CellPopulation`.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "efd30179", + "metadata": {}, + "outputs": [], + "source": [ + "cell_population = chaste.cell_based.PottsBasedCellPopulation[2](mesh, cells)" + ] + }, + { + "cell_type": "markdown", + "id": "8f647aa0", + "metadata": {}, + "source": [ + "In order to visualize labelled cells we need to use the following command.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "98b9939b", + "metadata": {}, + "outputs": [], + "source": [ + "cell_population.AddCellWriterCellLabelWriter()" + ] + }, + { + "cell_type": "markdown", + "id": "522d2306", + "metadata": {}, + "source": [ + "PyChaste can do simple 3D rendering with VTK. We set up a VtkScene so that we can\n", + "see the population evovle in real time.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0bdc223b", + "metadata": {}, + "outputs": [], + "source": [ + "scene = chaste.visualization.VtkScene[2]()\n", + "scene.SetCellPopulation(cell_population)\n", + "scene.GetCellPopulationActorGenerator().SetShowPottsMeshEdges(True)\n", + "nb_manager = chaste.visualization.JupyterNotebookManager()\n", + "nb_manager.vtk_show(scene, height=600)" + ] + }, + { + "cell_type": "markdown", + "id": "a837451f", + "metadata": {}, + "source": [ + "We then pass in the cell population into an `OffLatticeSimulation`, and set the output directory and end time\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e47c4a07", + "metadata": {}, + "outputs": [], + "source": [ + "simulator = chaste.cell_based.OnLatticeSimulation[2](cell_population)\n", + "simulator.SetOutputDirectory(\"Python/TestCellSorting\")\n", + "simulator.SetEndTime(20.0)\n", + "simulator.SetSamplingTimestepMultiple(10)" + ] + }, + { + "cell_type": "markdown", + "id": "3b5c4520", + "metadata": {}, + "source": [ + "We must now create one or more update rules, which determine the Hamiltonian in the Potts simulation.\n", + "For this test, we use two update rules based upon a volume constraint (`VolumeConstraintPottsUpdateRule`) and\n", + "differential adhesion between cells (`DifferentialAdhesionPottsUpdateRule`), set appropriate parameters, and\n", + "pass them to the `OnLatticeSimulation`.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0bd2a82e", + "metadata": {}, + "outputs": [], + "source": [ + "volume_constraint_update_rule = chaste.cell_based.VolumeConstraintPottsUpdateRule[2]()\n", + "volume_constraint_update_rule.SetMatureCellTargetVolume(16)\n", + "volume_constraint_update_rule.SetDeformationEnergyParameter(0.2)\n", + "simulator.AddUpdateRule(volume_constraint_update_rule)" + ] + }, + { + "cell_type": "markdown", + "id": "9b4e9fd4", + "metadata": {}, + "source": [ + "We repeat the process for any other update rules.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "147a39c2", + "metadata": {}, + "outputs": [], + "source": [ + "differential_adhesion_update_rule = chaste.cell_based.DifferentialAdhesionPottsUpdateRule[2]()\n", + "differential_adhesion_update_rule.SetLabelledCellLabelledCellAdhesionEnergyParameter(0.16)\n", + "differential_adhesion_update_rule.SetLabelledCellCellAdhesionEnergyParameter(0.11)\n", + "differential_adhesion_update_rule.SetCellCellAdhesionEnergyParameter(0.02)\n", + "differential_adhesion_update_rule.SetLabelledCellBoundaryAdhesionEnergyParameter(0.16)\n", + "differential_adhesion_update_rule.SetCellBoundaryAdhesionEnergyParameter(0.16)\n", + "simulator.AddUpdateRule(differential_adhesion_update_rule)" + ] + }, + { + "cell_type": "markdown", + "id": "68f51df6", + "metadata": {}, + "source": [ + "Set up plotting\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0b3cf0ea", + "metadata": {}, + "outputs": [], + "source": [ + "scene_modifier = chaste.cell_based.VtkSceneModifier[2]()\n", + "scene_modifier.SetVtkScene(scene)\n", + "scene_modifier.SetUpdateFrequency(1000)\n", + "simulator.AddSimulationModifier(scene_modifier)" + ] + }, + { + "cell_type": "markdown", + "id": "0f9efe4d", + "metadata": {}, + "source": [ + "To run the simulation, we call `Solve()`.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2ce5716f", + "metadata": {}, + "outputs": [], + "source": [ + "scene.Start()\n", + "simulator.Solve()\n", + "chaste.cell_based.TearDownNotebookTest() # Tear down the test" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pychaste/src/py/doc/tutorial/CellSorting.md b/pychaste/src/py/doc/tutorial/CellSorting.md new file mode 100644 index 0000000000..a43d8968d1 --- /dev/null +++ b/pychaste/src/py/doc/tutorial/CellSorting.md @@ -0,0 +1,206 @@ +--- +title : "Cell Sorting" +summary: "" +draft: false +images: [] +toc: true +layout: "single" +--- +This tutorial is automatically generated from [TestPyCellSortingTutorial.py](https://github.com/Chaste/Chaste/blob/develop/pychaste/test/tutorial/TestPyCellSortingTutorial.py) at revision [4045f91a83f5](https://github.com/Chaste/Chaste/commit/4045f91a83f55dc4a97f2ca4f97b0c32f4e43a4a). + +Note that the code is given in full at the bottom of the page. + +## Introduction +This test is a demonstration of cell sorting using a Cellular Potts based framework. +It shows: + * How to set up a Potts simulation + * Working with labels + +## The Test + +```python +import unittest # Python testing framework + +import chaste # The PyChaste module +import chaste.cell_based # Contains cell populations +import chaste.mesh # Contains meshes +import chaste.visualization # Visualization tools +import matplotlib.pyplot as plt # Plotting +import numpy as np # Matrix tools + +class TestPyCellSortingTutorial(chaste.cell_based.AbstractCellBasedTestSuite): +``` +### Test 1 - Cell sorting +The next test generates a collection of cells, there are two types of cells, labelled ones and non labelled ones, +there is differential adhesion between the cell types. For the parameters specified, the cells sort into separate types. + +```python + def test_potts_monolayer_cell_sorting(self): + + # JUPYTER_SETUP +``` +First, we generate a `Potts` mesh. To create a `PottsMesh`, we can use the `PottsMeshGenerator`. +This generates a regular square-shaped mesh, in which all elements are the same size. +We have chosen an 8 by 8 block of elements each consisting of 4 by 4 ( = 16) lattice sites. + +```python + generator = chaste.mesh.PottsMeshGenerator[2](50, 8, 4, 50, 8, 4) + mesh = generator.GetMesh() +``` +Having created a mesh, we now create some cells. To do this, we the `CellsGenerator` helper class, +as before but this time the third argument is set to make all cells non-proliferative. + +```python + differentiated_type = chaste.cell_based.DifferentiatedCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), differentiated_type) +``` +Before we make a CellPopulation we make a cell label and then assign this label to some randomly chosen cells. + +```python + label = chaste.cell_based.CellLabel() + for eachCell in cells: + if chaste.core.RandomNumberGenerator.Instance().ranf() < 0.5: + eachCell.AddCellProperty(label) +``` +Now we have a mesh and a set of cells to go with it, we can create a `CellPopulation`. + +```python + cell_population = chaste.cell_based.PottsBasedCellPopulation[2](mesh, cells) +``` +In order to visualize labelled cells we need to use the following command. + +```python + cell_population.AddCellWriterCellLabelWriter() +``` +PyChaste can do simple 3D rendering with VTK. We set up a VtkScene so that we can +see the population evovle in real time. + +```python + scene = chaste.visualization.VtkScene[2]() + scene.SetCellPopulation(cell_population) + scene.GetCellPopulationActorGenerator().SetShowPottsMeshEdges(True) + # JUPYTER_SHOW_FIRST + scene.Start() # JUPYTER_SHOW +``` +We then pass in the cell population into an `OffLatticeSimulation`, and set the output directory and end time + +```python + simulator = chaste.cell_based.OnLatticeSimulation[2](cell_population) + simulator.SetOutputDirectory("Python/TestCellSorting") + simulator.SetEndTime(20.0) + simulator.SetSamplingTimestepMultiple(10) +``` +We must now create one or more update rules, which determine the Hamiltonian in the Potts simulation. +For this test, we use two update rules based upon a volume constraint (`VolumeConstraintPottsUpdateRule`) and +differential adhesion between cells (`DifferentialAdhesionPottsUpdateRule`), set appropriate parameters, and +pass them to the `OnLatticeSimulation`. + +```python + volume_constraint_update_rule = chaste.cell_based.VolumeConstraintPottsUpdateRule[2]() + volume_constraint_update_rule.SetMatureCellTargetVolume(16) + volume_constraint_update_rule.SetDeformationEnergyParameter(0.2) + simulator.AddUpdateRule(volume_constraint_update_rule) +``` +We repeat the process for any other update rules. + +```python + differential_adhesion_update_rule = chaste.cell_based.DifferentialAdhesionPottsUpdateRule[2]() + differential_adhesion_update_rule.SetLabelledCellLabelledCellAdhesionEnergyParameter(0.16) + differential_adhesion_update_rule.SetLabelledCellCellAdhesionEnergyParameter(0.11) + differential_adhesion_update_rule.SetCellCellAdhesionEnergyParameter(0.02) + differential_adhesion_update_rule.SetLabelledCellBoundaryAdhesionEnergyParameter(0.16) + differential_adhesion_update_rule.SetCellBoundaryAdhesionEnergyParameter(0.16) + simulator.AddUpdateRule(differential_adhesion_update_rule) +``` +Set up plotting + +```python + scene_modifier = chaste.cell_based.VtkSceneModifier[2]() + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(1000) + simulator.AddSimulationModifier(scene_modifier) +``` +To run the simulation, we call `Solve()`. + +```python + scene.Start() + simulator.Solve() + + # JUPYTER_TEARDOWN + +if __name__ == "__main__": + unittest.main(verbosity=2) +``` + +## Full code + +```python +import unittest # Python testing framework + +import chaste # The PyChaste module +import chaste.cell_based # Contains cell populations +import chaste.mesh # Contains meshes +import chaste.visualization # Visualization tools +import matplotlib.pyplot as plt # Plotting +import numpy as np # Matrix tools + +class TestPyCellSortingTutorial(chaste.cell_based.AbstractCellBasedTestSuite): + + def test_potts_monolayer_cell_sorting(self): + + # JUPYTER_SETUP + + generator = chaste.mesh.PottsMeshGenerator[2](50, 8, 4, 50, 8, 4) + mesh = generator.GetMesh() + + differentiated_type = chaste.cell_based.DifferentiatedCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), differentiated_type) + + label = chaste.cell_based.CellLabel() + for eachCell in cells: + if chaste.core.RandomNumberGenerator.Instance().ranf() < 0.5: + eachCell.AddCellProperty(label) + + cell_population = chaste.cell_based.PottsBasedCellPopulation[2](mesh, cells) + + cell_population.AddCellWriterCellLabelWriter() + + scene = chaste.visualization.VtkScene[2]() + scene.SetCellPopulation(cell_population) + scene.GetCellPopulationActorGenerator().SetShowPottsMeshEdges(True) + # JUPYTER_SHOW_FIRST + scene.Start() # JUPYTER_SHOW + + simulator = chaste.cell_based.OnLatticeSimulation[2](cell_population) + simulator.SetOutputDirectory("Python/TestCellSorting") + simulator.SetEndTime(20.0) + simulator.SetSamplingTimestepMultiple(10) + + volume_constraint_update_rule = chaste.cell_based.VolumeConstraintPottsUpdateRule[2]() + volume_constraint_update_rule.SetMatureCellTargetVolume(16) + volume_constraint_update_rule.SetDeformationEnergyParameter(0.2) + simulator.AddUpdateRule(volume_constraint_update_rule) + + differential_adhesion_update_rule = chaste.cell_based.DifferentialAdhesionPottsUpdateRule[2]() + differential_adhesion_update_rule.SetLabelledCellLabelledCellAdhesionEnergyParameter(0.16) + differential_adhesion_update_rule.SetLabelledCellCellAdhesionEnergyParameter(0.11) + differential_adhesion_update_rule.SetCellCellAdhesionEnergyParameter(0.02) + differential_adhesion_update_rule.SetLabelledCellBoundaryAdhesionEnergyParameter(0.16) + differential_adhesion_update_rule.SetCellBoundaryAdhesionEnergyParameter(0.16) + simulator.AddUpdateRule(differential_adhesion_update_rule) + + scene_modifier = chaste.cell_based.VtkSceneModifier[2]() + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(1000) + simulator.AddSimulationModifier(scene_modifier) + + scene.Start() + simulator.Solve() + + # JUPYTER_TEARDOWN + +if __name__ == "__main__": + unittest.main(verbosity=2) +``` diff --git a/pychaste/src/py/doc/tutorial/ImmersedBoundary.ipynb b/pychaste/src/py/doc/tutorial/ImmersedBoundary.ipynb new file mode 100644 index 0000000000..8687e8d1d4 --- /dev/null +++ b/pychaste/src/py/doc/tutorial/ImmersedBoundary.ipynb @@ -0,0 +1,1094 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "e9154dfd", + "metadata": {}, + "source": [ + "This tutorial is automatically generated from [TestPyImmersedBoundaryTutorial.py](https://github.com/Chaste/Chaste/blob/develop/pychaste/test/tutorial/TestPyImmersedBoundaryTutorial.py) at revision [4045f91a83f5](https://github.com/Chaste/Chaste/commit/4045f91a83f55dc4a97f2ca4f97b0c32f4e43a4a)." + ] + }, + { + "cell_type": "markdown", + "id": "afe6192f", + "metadata": {}, + "source": [ + "\n", + "## Introduction\n", + "This tutorial is a demonstration of the immersed boundary method, a technique\n", + "for simulating fluid-structure interactions. We can use the immersed boundary\n", + "method to simulate a cell as a structure with its outer **boundary immersed**\n", + "in a fluid. There is a two-way coupling between the fluid and the structure:\n", + "the flow of the fluid exerts a force on the structure, and the structure\n", + "influences the flow of the fluid.\n", + "\n", + "In this tutorial, we demonstrate:\n", + "1. Building single-cell immersed boundary capable simulations.\n", + "2. Building multi-cellular immersed boundary simulations.\n", + "3. Adding and manipulating immersed boundary fluid sources.\n", + "\n", + "## Imports\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5d150229", + "metadata": {}, + "outputs": [], + "source": [ + "import chaste\n", + "from chaste.cell_based import (\n", + "AbstractCellBasedTestSuite,\n", + "CellsGenerator,\n", + "DifferentiatedCellProliferativeType,\n", + "ForwardEulerNumericalMethod,\n", + "ImmersedBoundaryCellPopulation,\n", + "ImmersedBoundaryLinearInteractionForce,\n", + "ImmersedBoundaryLinearMembraneForce,\n", + "ImmersedBoundarySimulationModifier,\n", + "OffLatticeSimulation,\n", + ")\n", + "from chaste.mesh import (\n", + "FluidSource,\n", + "ImmersedBoundaryPalisadeMeshGenerator,\n", + ")\n", + "from chaste.visualization import (\n", + "JupyterNotebookManager,\n", + "JupyterSceneModifier,\n", + "VtkScene,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "c89f8cab", + "metadata": {}, + "source": [ + "### 1. Simple Immersed Boundary Simulations\n", + "We begin by exploring simulations containing a single cell. This will\n", + "familiarise you with how to generate immersed boundary cells, the steps\n", + "involved in setting up an immersed boundary simulation, and the options\n", + "available for controlling how the cells are generated and behave.\n", + "\n", + "Immersed boundary simulations operate over a square domain, with `x` and `y`\n", + "coordinates lying in the range `0` to `1`. The domain wraps on both axes -\n", + "this means that if a cell moves off the right hand edge of the domain,\n", + "the segment will appear on the left hand side. This is not purely visual;\n", + "forces are also transmitted across these boundaries.\n", + "\n", + " **Tip** Make sure all your coordinates are between `0` and `1`.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "b7b02b84", + "metadata": {}, + "source": [ + "Setup the simulation environment in the notebook\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10c7f7f1", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.cell_based.SetupNotebookTest() # Set up the test" + ] + }, + { + "cell_type": "markdown", + "id": "0a768881", + "metadata": {}, + "source": [ + "Next, we define the necessary geometry by generating a mesh to\n", + "contain a single cell.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a50e8ed7", + "metadata": {}, + "outputs": [], + "source": [ + "gen = ImmersedBoundaryPalisadeMeshGenerator(1, 128, 0.1, 2.0, 0.0, False)\n", + "mesh = gen.GetMesh()" + ] + }, + { + "cell_type": "markdown", + "id": "df234da8", + "metadata": {}, + "source": [ + "The first line of code defines an `ImmersedBoundaryPalisadeMeshGenerator`\n", + "called `gen`. The 3rd parameter controls the exponent of the superellipse(`0.1`)\n", + "and the 4th parameter controls the aspect ratio of the cell(`2.0`). You can\n", + "experiment with modifying these to change the initial shape of the cell.\n", + "\n", + "The second line of code instructs the mesh generator to generate a mesh.\n", + "Checking the type of mesh with `type(mesh)` will show it as\n", + "`ImmersedBoundaryMesh_2_2`. The `_2_2` suffix denotes that we are using\n", + "a 2-dimensional space, and 2-dimensional elements to define the mesh.\n", + "\n", + "We now set the fluid grid resolution. The following code specifies\n", + "that we are using a 64x64 grid to simulate our fluid over.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14b88407", + "metadata": {}, + "outputs": [], + "source": [ + "mesh.SetNumGridPtsXAndY(64)" + ] + }, + { + "cell_type": "markdown", + "id": "ba0c77fa", + "metadata": {}, + "source": [ + "Next, we generate the cells. We specify a cell type and cell cycle model.\n", + "These can be changed to modify the life cycle of the cells. The\n", + "cell generator then constructs the necessary information for each\n", + "of the elements in the mesh.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5688d238", + "metadata": {}, + "outputs": [], + "source": [ + "cell_type = DifferentiatedCellProliferativeType()\n", + "cell_generator = CellsGenerator[\"UniformCellCycleModel\", 2]()\n", + "cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), cell_type)" + ] + }, + { + "cell_type": "markdown", + "id": "ab4c371f", + "metadata": {}, + "source": [ + "Finally, we construct the cell population. We then specify whether the\n", + "population has active fluid sources or not. For now, we are not\n", + "using any fluid sources, so we set this to `False`\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "985c7e49", + "metadata": {}, + "outputs": [], + "source": [ + "cell_population = ImmersedBoundaryCellPopulation[2](mesh, cells)\n", + "cell_population.SetIfPopulationHasActiveSources(False)" + ] + }, + { + "cell_type": "markdown", + "id": "4b2524c0", + "metadata": {}, + "source": [ + "We can make a quick visualization of the cell population\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b400ca25", + "metadata": {}, + "outputs": [], + "source": [ + "scene = VtkScene[2]()\n", + "scene.SetCellPopulation(cell_population)\n", + "nb_manager = JupyterNotebookManager()\n", + "nb_manager.vtk_show(scene, height=300)" + ] + }, + { + "cell_type": "markdown", + "id": "f6aca627", + "metadata": {}, + "source": [ + "Next, we create an `OffLatticeSimulation` simulator to control the\n", + "simulation. Although the fluid is simulated on a lattice (grid),\n", + "the nodes/cells are not bound to a lattice.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4c24b119", + "metadata": {}, + "outputs": [], + "source": [ + "simulator = OffLatticeSimulation[2, 2](cell_population)\n", + "simulator.SetNumericalMethod(ForwardEulerNumericalMethod[2, 2]())\n", + "simulator.GetNumericalMethod().SetUseUpdateNodeLocation(True)" + ] + }, + { + "cell_type": "markdown", + "id": "079126a5", + "metadata": {}, + "source": [ + "As we have an off-lattice simulation, we need a way to model the\n", + "fluid. This is handled by the `ImmersedBoundarySimulationModifier`.\n", + "Modifiers in Chaste are classes that can be attached to simulations\n", + "to perform some additional custom functionality each timestep.\n", + "In this case, the modifier is responsible for solving the\n", + "Navier-Stokes equations and propagating forces between the nodes and\n", + "the fluid.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "677ef819", + "metadata": {}, + "outputs": [], + "source": [ + "ib_modifier = ImmersedBoundarySimulationModifier[2]()\n", + "simulator.AddSimulationModifier(ib_modifier)" + ] + }, + { + "cell_type": "markdown", + "id": "f5f5037f", + "metadata": {}, + "source": [ + "We must also provide the modifier with a force model to govern\n", + "interactions between the nodes forming the cell membrane.\n", + "Note that these forces only act between nodes in the same cell;\n", + "they do not control interactions between cells.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f05d519e", + "metadata": {}, + "outputs": [], + "source": [ + "membrane_force = ImmersedBoundaryLinearMembraneForce[2]()\n", + "membrane_force.SetElementSpringConst(1.0 * 1e7)\n", + "ib_modifier.AddImmersedBoundaryForce(membrane_force)" + ] + }, + { + "cell_type": "markdown", + "id": "bb167b17", + "metadata": {}, + "source": [ + "The `ImmersedBoundaryLinearMembraneForce` models forces between\n", + "membrane nodes using linear springs i.e, the force applied is\n", + "proportional to the deviation of the distance between nodes\n", + "from a rest length. The spring constant(`1.0 * 1e7`) defines how\n", + "stiff the cell boundary is.\n", + "\n", + " **Practice** Experiment with adjusting the spring constant to\n", + " change the force behaviour between nodes of the cell boundary.\n", + " \n", + "Next, we set the simulation properties\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c10e1eb1", + "metadata": {}, + "outputs": [], + "source": [ + "dt = 0.05\n", + "simulator.SetOutputDirectory(\"Python/TestImmersedBoundary_1\")\n", + "simulator.SetDt(dt)\n", + "simulator.SetSamplingTimestepMultiple(4)\n", + "simulator.SetEndTime(1000 * dt)" + ] + }, + { + "cell_type": "markdown", + "id": "cbb2c821", + "metadata": {}, + "source": [ + "We can add a modifier to visualize the cell population while the\n", + "simulation is in progress\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f185d7a5", + "metadata": {}, + "outputs": [], + "source": [ + "scene_modifier = JupyterSceneModifier[2](nb_manager)\n", + "scene_modifier.SetVtkScene(scene)\n", + "scene_modifier.SetUpdateFrequency(1000)\n", + "simulator.AddSimulationModifier(scene_modifier)" + ] + }, + { + "cell_type": "markdown", + "id": "17468410", + "metadata": {}, + "source": [ + "Finally, to run the simulation we call the `Solve()` method.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ae231668", + "metadata": {}, + "outputs": [], + "source": [ + "simulator.Solve()" + ] + }, + { + "cell_type": "markdown", + "id": "1059f034", + "metadata": {}, + "source": [ + "Reset the simulation environment in the notebook\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ec0ba71f", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.cell_based.TearDownNotebookTest() # Tear down the test" + ] + }, + { + "cell_type": "markdown", + "id": "beb57242", + "metadata": {}, + "source": [ + "### 2. Adding More Cells\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "baf830f4", + "metadata": {}, + "source": [ + "#### Multiple Cells\n", + "\n", + "Setup the simulation environment in the notebook\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "75125e6a", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.cell_based.SetupNotebookTest() # Set up the test" + ] + }, + { + "cell_type": "markdown", + "id": "e76d8547", + "metadata": {}, + "source": [ + "We can use the mesh generator to generate multiple cells. The first\n", + "parameter of the mesh generator constructor controls the number of\n", + "cells.\n", + "\n", + " **Practice** Try increasing the number of cells by adjusting the\n", + " parameter value. A sensible range for this tutorial is 4-10 cells.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8692911d", + "metadata": {}, + "outputs": [], + "source": [ + "gen = ImmersedBoundaryPalisadeMeshGenerator(5, 128, 0.1, 2.0, 0.0, False)" + ] + }, + { + "cell_type": "markdown", + "id": "7874df06", + "metadata": {}, + "source": [ + "#### Laminas\n", + "In addition to the cells we have seen so far, we can introduce\n", + "laminas to the simulation. Laminas are surfaces with reduced\n", + "dimensionality. For 3D elements, a lamina is a 2D surface. For the\n", + "2D elements we are currently working with, laminas are lines.\n", + "Changing the last parameter of the mesh generator constructor from `False`\n", + "to `True` will generate a basal lamina spanning the palisade cells.\n", + "Laminas can also interact with the fluid field, and can be made\n", + "\"leaky\" to allow some flow across their boundary. This can be used\n", + "to model a permeable boundary.\n", + "\n", + " **Practice** Try changing the 6th constructor parameter to create a lamina.\n", + " \n", + "#### Cell Variations\n", + "Apart from using the 3rd and 4th constructor parameters to modify\n", + "the cell shapes, we can also introduce variation between cells by\n", + "modifying the 5th parameter.\n", + "\n", + " **Practice** Try adjusting the 3rd and 4th constructor parameters to\n", + " introduce cell variations.\n", + " \n", + "Next, we generate the mesh and set the fluid grid resolution\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "206a4b42", + "metadata": {}, + "outputs": [], + "source": [ + "mesh = gen.GetMesh()\n", + "mesh.SetNumGridPtsXAndY(64)" + ] + }, + { + "cell_type": "markdown", + "id": "3a2883aa", + "metadata": {}, + "source": [ + "Below, we generate the cells\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "965b303b", + "metadata": {}, + "outputs": [], + "source": [ + "cell_type = DifferentiatedCellProliferativeType()\n", + "cell_generator = CellsGenerator[\"UniformCellCycleModel\", 2]()\n", + "cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), cell_type)" + ] + }, + { + "cell_type": "markdown", + "id": "d277660b", + "metadata": {}, + "source": [ + "Then we set up the cell population with no active fluid sources\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7caa5dfe", + "metadata": {}, + "outputs": [], + "source": [ + "cell_population = ImmersedBoundaryCellPopulation[2](mesh, cells)\n", + "cell_population.SetIfPopulationHasActiveSources(False)" + ] + }, + { + "cell_type": "markdown", + "id": "dc332649", + "metadata": {}, + "source": [ + "We can visualize the cell population below\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3b1e4a75", + "metadata": {}, + "outputs": [], + "source": [ + "scene = VtkScene[2]()\n", + "scene.SetCellPopulation(cell_population)\n", + "nb_manager = JupyterNotebookManager()\n", + "nb_manager.vtk_show(scene, height=300)" + ] + }, + { + "cell_type": "markdown", + "id": "4973f443", + "metadata": {}, + "source": [ + "Now we create a simulator to manage the simulation\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "78e04ee7", + "metadata": {}, + "outputs": [], + "source": [ + "simulator = OffLatticeSimulation[2, 2](cell_population)\n", + "simulator.SetNumericalMethod(ForwardEulerNumericalMethod[2, 2]())\n", + "simulator.GetNumericalMethod().SetUseUpdateNodeLocation(True)" + ] + }, + { + "cell_type": "markdown", + "id": "47886f23", + "metadata": {}, + "source": [ + "We add an immersed boundary simulation modifier to the simulator\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d8606a9e", + "metadata": {}, + "outputs": [], + "source": [ + "ib_modifier = ImmersedBoundarySimulationModifier[2]()\n", + "simulator.AddSimulationModifier(ib_modifier)" + ] + }, + { + "cell_type": "markdown", + "id": "ab438d40", + "metadata": {}, + "source": [ + "We then add a force law to the simulation modifier to model the\n", + "behaviour of the cell membrane\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c8e9afce", + "metadata": {}, + "outputs": [], + "source": [ + "membrane_force = ImmersedBoundaryLinearMembraneForce[2]()\n", + "membrane_force.SetElementSpringConst(1.0 * 1e7)\n", + "ib_modifier.AddImmersedBoundaryForce(membrane_force)" + ] + }, + { + "cell_type": "markdown", + "id": "b19410d9", + "metadata": {}, + "source": [ + "#### Inter-cellular Interactions\n", + "So far, we have encountered forces that act to maintain the shape\n", + "of the cell membrane. We can also introduce an inter-cellular\n", + "force law using `ImmersedBoundaryLinearInteractionForce`.\n", + "This has a `SetSpringConst` method instead of a `SetElementSpringConst`\n", + "method. It also has a `SetRestLength` method that we can use to\n", + "modify the rest length.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "add3b4e4", + "metadata": {}, + "outputs": [], + "source": [ + "interaction_force = ImmersedBoundaryLinearInteractionForce[2]()\n", + "interaction_force.SetSpringConst(1.0 * 1e6)\n", + "ib_modifier.AddImmersedBoundaryForce(interaction_force)" + ] + }, + { + "cell_type": "markdown", + "id": "6425a751", + "metadata": {}, + "source": [ + "Next, we set the simulation properties\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "31107a38", + "metadata": {}, + "outputs": [], + "source": [ + "dt = 0.05\n", + "simulator.SetOutputDirectory(\"Python/TestImmersedBoundary_2\")\n", + "simulator.SetDt(dt)\n", + "simulator.SetSamplingTimestepMultiple(4)\n", + "simulator.SetEndTime(1000 * dt)" + ] + }, + { + "cell_type": "markdown", + "id": "003976a8", + "metadata": {}, + "source": [ + "Finally, we run the simulation\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "97b77bfb", + "metadata": {}, + "outputs": [], + "source": [ + "simulator.Solve()" + ] + }, + { + "cell_type": "markdown", + "id": "60c980e7", + "metadata": {}, + "source": [ + "We can visualize the end state of the cell population\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "673b1d8c", + "metadata": {}, + "outputs": [], + "source": [ + "nb_manager.vtk_show(scene, height=300)" + ] + }, + { + "cell_type": "markdown", + "id": "2a758ff7", + "metadata": {}, + "source": [ + "Reset the simulation environment in the notebook\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1567c495", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.cell_based.TearDownNotebookTest() # Tear down the test" + ] + }, + { + "cell_type": "markdown", + "id": "54bdb70f", + "metadata": {}, + "source": [ + "### 3. Adding Fluid Sources\n", + "Now that we are familiar with how to generate the cells, we will\n", + "introduce fluid sources.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "6df30465", + "metadata": {}, + "source": [ + "#### Adding a Fluid Source\n", + "\n", + "Setup the simulation environment in the notebook\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7e2385a2", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.cell_based.SetupNotebookTest() # Set up the test" + ] + }, + { + "cell_type": "markdown", + "id": "39371bf6", + "metadata": {}, + "source": [ + "We begin by constructing a fluid source object:\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d30e65f5", + "metadata": {}, + "outputs": [], + "source": [ + "source = FluidSource[2](0, 0.5, 0.7)" + ] + }, + { + "cell_type": "markdown", + "id": "75c9e2b2", + "metadata": {}, + "source": [ + "This constructs a `FluidSource` object in 2 dimensions. The first\n", + "parameter supplies the index of the fluid source. Each source we\n", + "create must have a unique index. The next two parameters are the\n", + "`x` and `y` coordinates of the source. Fluid sources in Chaste are\n", + "point-like, that is to say they do not have any area/volume.\n", + "\n", + "Having created the fluid source, we set its strength:\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "51cb8499", + "metadata": {}, + "outputs": [], + "source": [ + "source.SetStrength(0.012)" + ] + }, + { + "cell_type": "markdown", + "id": "969547ef", + "metadata": {}, + "source": [ + "Next, we create the mesh\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3e45ed26", + "metadata": {}, + "outputs": [], + "source": [ + "gen = ImmersedBoundaryPalisadeMeshGenerator(5, 128, 0.1, 2.0, 0.0, False)\n", + "mesh = gen.GetMesh()\n", + "mesh.SetNumGridPtsXAndY(64)" + ] + }, + { + "cell_type": "markdown", + "id": "a8d56bcb", + "metadata": {}, + "source": [ + "We must associate the source with an element in the simulation\n", + "so that the simulation is aware of the source.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7e598de2", + "metadata": {}, + "outputs": [], + "source": [ + "mesh.GetElement(0).SetFluidSource(source)" + ] + }, + { + "cell_type": "markdown", + "id": "465304b1", + "metadata": {}, + "source": [ + "We now generate the cells\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d787ea41", + "metadata": {}, + "outputs": [], + "source": [ + "cell_type = DifferentiatedCellProliferativeType()\n", + "cell_generator = CellsGenerator[\"UniformCellCycleModel\", 2]()\n", + "cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), cell_type)" + ] + }, + { + "cell_type": "markdown", + "id": "1e65391d", + "metadata": {}, + "source": [ + "Then we set up the cell population\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c1bf2659", + "metadata": {}, + "outputs": [], + "source": [ + "cell_population = ImmersedBoundaryCellPopulation[2](mesh, cells)" + ] + }, + { + "cell_type": "markdown", + "id": "63077191", + "metadata": {}, + "source": [ + "Finally, we must tell the cell population that fluid sources are present.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "686970f9", + "metadata": {}, + "outputs": [], + "source": [ + "cell_population.SetIfPopulationHasActiveSources(True)" + ] + }, + { + "cell_type": "markdown", + "id": "223ed9c9", + "metadata": {}, + "source": [ + "#### Varying the Source Location and Strength\n", + " **Practice** You can experiment with the source location. Try moving it\n", + " closer to and further away from the cells.\n", + " \n", + " **Practice** Try modifying the source strength to see what impact this\n", + " has on the cell shapes.\n", + " \n", + "Below, we visualize the cell population\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5a4721b1", + "metadata": {}, + "outputs": [], + "source": [ + "scene = VtkScene[2]()\n", + "scene.SetCellPopulation(cell_population)\n", + "nb_manager = JupyterNotebookManager()\n", + "nb_manager.vtk_show(scene, height=300)" + ] + }, + { + "cell_type": "markdown", + "id": "a6f3d00a", + "metadata": {}, + "source": [ + "Create a simulator to manage the simulation\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "91cb39a8", + "metadata": {}, + "outputs": [], + "source": [ + "simulator = OffLatticeSimulation[2, 2](cell_population)\n", + "simulator.SetNumericalMethod(ForwardEulerNumericalMethod[2, 2]())\n", + "simulator.GetNumericalMethod().SetUseUpdateNodeLocation(True)" + ] + }, + { + "cell_type": "markdown", + "id": "d706bbb0", + "metadata": {}, + "source": [ + "Add an immersed boundary simulation modifier\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ec2ca4e0", + "metadata": {}, + "outputs": [], + "source": [ + "ib_modifier = ImmersedBoundarySimulationModifier[2]()\n", + "simulator.AddSimulationModifier(ib_modifier)" + ] + }, + { + "cell_type": "markdown", + "id": "0fbe5db9", + "metadata": {}, + "source": [ + "#### Fluid-Cell Interaction\n", + " **Practice** Try modifying the spring constant of the\n", + " `ImmersedBoundaryLinearMembraneForce` to see how this changes the\n", + " effect of the fluid source on the cells.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "851233c8", + "metadata": {}, + "outputs": [], + "source": [ + "membrane_force = ImmersedBoundaryLinearMembraneForce[2]()\n", + "membrane_force.SetElementSpringConst(1.0 * 1e7)\n", + "ib_modifier.AddImmersedBoundaryForce(membrane_force)" + ] + }, + { + "cell_type": "markdown", + "id": "e22532bc", + "metadata": {}, + "source": [ + "Add an inter-cellular force law\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0ff9bfd1", + "metadata": {}, + "outputs": [], + "source": [ + "interaction_force = ImmersedBoundaryLinearInteractionForce[2]()\n", + "interaction_force.SetSpringConst(1.0 * 1e6)\n", + "ib_modifier.AddImmersedBoundaryForce(interaction_force)" + ] + }, + { + "cell_type": "markdown", + "id": "3985c0a1", + "metadata": {}, + "source": [ + "#### Adding More Sources\n", + " **Practice** Try adding a second fluid source. You will need to\n", + " use a unique index, and attach it to a different element as\n", + " each element can only manage a single fluid source.\n", + " \n", + "Next, we set the simulation properties\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8cf2f879", + "metadata": {}, + "outputs": [], + "source": [ + "dt = 0.05\n", + "simulator.SetOutputDirectory(\"Python/TestImmersedBoundary_3\")\n", + "simulator.SetDt(dt)\n", + "simulator.SetSamplingTimestepMultiple(4)\n", + "simulator.SetEndTime(300 * dt)" + ] + }, + { + "cell_type": "markdown", + "id": "c939d04c", + "metadata": {}, + "source": [ + "Finally, we run the simulation\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8028d32f", + "metadata": {}, + "outputs": [], + "source": [ + "simulator.Solve()" + ] + }, + { + "cell_type": "markdown", + "id": "66942811", + "metadata": {}, + "source": [ + "Then we visualize the end state\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6c0848d9", + "metadata": {}, + "outputs": [], + "source": [ + "nb_manager.vtk_show(scene, height=300)" + ] + }, + { + "cell_type": "markdown", + "id": "7ff117a7", + "metadata": {}, + "source": [ + "Reset the simulation environment in the notebook\n", + "JUPYTER_TEARDOWN\n", + "\n", + "#### Further Exercises\n", + " * Try integrating a different cell cycle model to introduce cell\n", + " division. See how the presence of a fluid source impacts the\n", + " structure that is formed.\n", + " * Use one of the cell writers to collect some statistics\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pychaste/src/py/doc/tutorial/ImmersedBoundary.md b/pychaste/src/py/doc/tutorial/ImmersedBoundary.md new file mode 100644 index 0000000000..04a88afc76 --- /dev/null +++ b/pychaste/src/py/doc/tutorial/ImmersedBoundary.md @@ -0,0 +1,649 @@ +--- +title : "Immersed Boundary" +summary: "" +draft: false +images: [] +toc: true +layout: "single" +--- +This tutorial is automatically generated from [TestPyImmersedBoundaryTutorial.py](https://github.com/Chaste/Chaste/blob/develop/pychaste/test/tutorial/TestPyImmersedBoundaryTutorial.py) at revision [4045f91a83f5](https://github.com/Chaste/Chaste/commit/4045f91a83f55dc4a97f2ca4f97b0c32f4e43a4a). + +Note that the code is given in full at the bottom of the page. + +## Introduction +This tutorial is a demonstration of the immersed boundary method, a technique +for simulating fluid-structure interactions. We can use the immersed boundary +method to simulate a cell as a structure with its outer **boundary immersed** +in a fluid. There is a two-way coupling between the fluid and the structure: +the flow of the fluid exerts a force on the structure, and the structure +influences the flow of the fluid. + +In this tutorial, we demonstrate: +1. Building single-cell immersed boundary capable simulations. +2. Building multi-cellular immersed boundary simulations. +3. Adding and manipulating immersed boundary fluid sources. + +## Imports + +```python +import unittest + +import chaste + +from chaste.cell_based import ( + AbstractCellBasedTestSuite, + CellsGenerator, + DifferentiatedCellProliferativeType, + ForwardEulerNumericalMethod, + ImmersedBoundaryCellPopulation, + ImmersedBoundaryLinearInteractionForce, + ImmersedBoundaryLinearMembraneForce, + ImmersedBoundarySimulationModifier, + OffLatticeSimulation, +) + +from chaste.mesh import ( + FluidSource, + ImmersedBoundaryPalisadeMeshGenerator, +) + +from chaste.visualization import ( + JupyterNotebookManager, + JupyterSceneModifier, + VtkScene, +) + +class TestPyImmersedBoundaryTutorial(AbstractCellBasedTestSuite): +``` +### 1. Simple Immersed Boundary Simulations +We begin by exploring simulations containing a single cell. This will +familiarise you with how to generate immersed boundary cells, the steps +involved in setting up an immersed boundary simulation, and the options +available for controlling how the cells are generated and behave. + +Immersed boundary simulations operate over a square domain, with `x` and `y` +coordinates lying in the range `0` to `1`. The domain wraps on both axes - +this means that if a cell moves off the right hand edge of the domain, +the segment will appear on the left hand side. This is not purely visual; +forces are also transmitted across these boundaries. + + **Tip** Make sure all your coordinates are between `0` and `1`. + +```python + def test_simple_immersed_boundary_simulation(self): +``` +Setup the simulation environment in the notebook + +```python + # JUPYTER_SETUP +``` +Next, we define the necessary geometry by generating a mesh to +contain a single cell. + +```python + gen = ImmersedBoundaryPalisadeMeshGenerator(1, 128, 0.1, 2.0, 0.0, False) + mesh = gen.GetMesh() +``` +The first line of code defines an `ImmersedBoundaryPalisadeMeshGenerator` +called `gen`. The 3rd parameter controls the exponent of the superellipse(`0.1`) +and the 4th parameter controls the aspect ratio of the cell(`2.0`). You can +experiment with modifying these to change the initial shape of the cell. + +The second line of code instructs the mesh generator to generate a mesh. +Checking the type of mesh with `type(mesh)` will show it as +`ImmersedBoundaryMesh_2_2`. The `_2_2` suffix denotes that we are using +a 2-dimensional space, and 2-dimensional elements to define the mesh. + +We now set the fluid grid resolution. The following code specifies +that we are using a 64x64 grid to simulate our fluid over. + +```python + mesh.SetNumGridPtsXAndY(64) +``` +Next, we generate the cells. We specify a cell type and cell cycle model. +These can be changed to modify the life cycle of the cells. The +cell generator then constructs the necessary information for each +of the elements in the mesh. + +```python + cell_type = DifferentiatedCellProliferativeType() + cell_generator = CellsGenerator["UniformCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), cell_type) +``` +Finally, we construct the cell population. We then specify whether the +population has active fluid sources or not. For now, we are not +using any fluid sources, so we set this to `False` + +```python + cell_population = ImmersedBoundaryCellPopulation[2](mesh, cells) + cell_population.SetIfPopulationHasActiveSources(False) +``` +We can make a quick visualization of the cell population + +```python + scene = VtkScene[2]() + scene.SetCellPopulation(cell_population) + nb_manager = JupyterNotebookManager() + nb_manager.vtk_show(scene, height=300) +``` +Next, we create an `OffLatticeSimulation` simulator to control the +simulation. Although the fluid is simulated on a lattice (grid), +the nodes/cells are not bound to a lattice. + +```python + simulator = OffLatticeSimulation[2, 2](cell_population) + simulator.SetNumericalMethod(ForwardEulerNumericalMethod[2, 2]()) + simulator.GetNumericalMethod().SetUseUpdateNodeLocation(True) +``` +As we have an off-lattice simulation, we need a way to model the +fluid. This is handled by the `ImmersedBoundarySimulationModifier`. +Modifiers in Chaste are classes that can be attached to simulations +to perform some additional custom functionality each timestep. +In this case, the modifier is responsible for solving the +Navier-Stokes equations and propagating forces between the nodes and +the fluid. + +```python + ib_modifier = ImmersedBoundarySimulationModifier[2]() + simulator.AddSimulationModifier(ib_modifier) +``` +We must also provide the modifier with a force model to govern +interactions between the nodes forming the cell membrane. +Note that these forces only act between nodes in the same cell; +they do not control interactions between cells. + +```python + membrane_force = ImmersedBoundaryLinearMembraneForce[2]() + membrane_force.SetElementSpringConst(1.0 * 1e7) + ib_modifier.AddImmersedBoundaryForce(membrane_force) +``` +The `ImmersedBoundaryLinearMembraneForce` models forces between +membrane nodes using linear springs i.e, the force applied is +proportional to the deviation of the distance between nodes +from a rest length. The spring constant(`1.0 * 1e7`) defines how +stiff the cell boundary is. + + **Practice** Experiment with adjusting the spring constant to + change the force behaviour between nodes of the cell boundary. + +Next, we set the simulation properties + +```python + dt = 0.05 + simulator.SetOutputDirectory("Python/TestImmersedBoundary_1") + simulator.SetDt(dt) + simulator.SetSamplingTimestepMultiple(4) + simulator.SetEndTime(1000 * dt) +``` +We can add a modifier to visualize the cell population while the +simulation is in progress + +```python + scene_modifier = JupyterSceneModifier[2](nb_manager) + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(1000) + simulator.AddSimulationModifier(scene_modifier) +``` +Finally, to run the simulation we call the `Solve()` method. + +```python + simulator.Solve() +``` +Reset the simulation environment in the notebook + +```python + # JUPYTER_TEARDOWN +``` +### 2. Adding More Cells + +```python + def test_multicell_immersed_boundary_simulation(self): +``` +#### Multiple Cells + +Setup the simulation environment in the notebook + +```python + # JUPYTER_SETUP +``` +We can use the mesh generator to generate multiple cells. The first +parameter of the mesh generator constructor controls the number of +cells. + + **Practice** Try increasing the number of cells by adjusting the + parameter value. A sensible range for this tutorial is 4-10 cells. + +```python + gen = ImmersedBoundaryPalisadeMeshGenerator(5, 128, 0.1, 2.0, 0.0, False) +``` +#### Laminas +In addition to the cells we have seen so far, we can introduce +laminas to the simulation. Laminas are surfaces with reduced +dimensionality. For 3D elements, a lamina is a 2D surface. For the +2D elements we are currently working with, laminas are lines. +Changing the last parameter of the mesh generator constructor from `False` +to `True` will generate a basal lamina spanning the palisade cells. +Laminas can also interact with the fluid field, and can be made +"leaky" to allow some flow across their boundary. This can be used +to model a permeable boundary. + + **Practice** Try changing the 6th constructor parameter to create a lamina. + +#### Cell Variations +Apart from using the 3rd and 4th constructor parameters to modify +the cell shapes, we can also introduce variation between cells by +modifying the 5th parameter. + + **Practice** Try adjusting the 3rd and 4th constructor parameters to + introduce cell variations. + +Next, we generate the mesh and set the fluid grid resolution + +```python + mesh = gen.GetMesh() + mesh.SetNumGridPtsXAndY(64) +``` +Below, we generate the cells + +```python + cell_type = DifferentiatedCellProliferativeType() + cell_generator = CellsGenerator["UniformCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), cell_type) +``` +Then we set up the cell population with no active fluid sources + +```python + cell_population = ImmersedBoundaryCellPopulation[2](mesh, cells) + cell_population.SetIfPopulationHasActiveSources(False) +``` +We can visualize the cell population below + +```python + scene = VtkScene[2]() + scene.SetCellPopulation(cell_population) + nb_manager = JupyterNotebookManager() + nb_manager.vtk_show(scene, height=300) +``` +Now we create a simulator to manage the simulation + +```python + simulator = OffLatticeSimulation[2, 2](cell_population) + simulator.SetNumericalMethod(ForwardEulerNumericalMethod[2, 2]()) + simulator.GetNumericalMethod().SetUseUpdateNodeLocation(True) +``` +We add an immersed boundary simulation modifier to the simulator + +```python + ib_modifier = ImmersedBoundarySimulationModifier[2]() + simulator.AddSimulationModifier(ib_modifier) +``` +We then add a force law to the simulation modifier to model the +behaviour of the cell membrane + +```python + membrane_force = ImmersedBoundaryLinearMembraneForce[2]() + membrane_force.SetElementSpringConst(1.0 * 1e7) + ib_modifier.AddImmersedBoundaryForce(membrane_force) +``` +#### Inter-cellular Interactions +So far, we have encountered forces that act to maintain the shape +of the cell membrane. We can also introduce an inter-cellular +force law using `ImmersedBoundaryLinearInteractionForce`. +This has a `SetSpringConst` method instead of a `SetElementSpringConst` +method. It also has a `SetRestLength` method that we can use to +modify the rest length. + +```python + interaction_force = ImmersedBoundaryLinearInteractionForce[2]() + interaction_force.SetSpringConst(1.0 * 1e6) + ib_modifier.AddImmersedBoundaryForce(interaction_force) +``` +Next, we set the simulation properties + +```python + dt = 0.05 + simulator.SetOutputDirectory("Python/TestImmersedBoundary_2") + simulator.SetDt(dt) + simulator.SetSamplingTimestepMultiple(4) + simulator.SetEndTime(1000 * dt) +``` +Finally, we run the simulation + +```python + simulator.Solve() +``` +We can visualize the end state of the cell population + +```python + nb_manager.vtk_show(scene, height=300) +``` +Reset the simulation environment in the notebook + +```python + # JUPYTER_TEARDOWN +``` +### 3. Adding Fluid Sources +Now that we are familiar with how to generate the cells, we will +introduce fluid sources. + +```python + def test_fluid_source_immersed_boundary_simulation(self): +``` +#### Adding a Fluid Source + +Setup the simulation environment in the notebook + +```python + # JUPYTER_SETUP +``` +We begin by constructing a fluid source object: + +```python + source = FluidSource[2](0, 0.5, 0.7) +``` +This constructs a `FluidSource` object in 2 dimensions. The first +parameter supplies the index of the fluid source. Each source we +create must have a unique index. The next two parameters are the +`x` and `y` coordinates of the source. Fluid sources in Chaste are +point-like, that is to say they do not have any area/volume. + +Having created the fluid source, we set its strength: + +```python + source.SetStrength(0.012) +``` +Next, we create the mesh + +```python + gen = ImmersedBoundaryPalisadeMeshGenerator(5, 128, 0.1, 2.0, 0.0, False) + mesh = gen.GetMesh() + mesh.SetNumGridPtsXAndY(64) +``` +We must associate the source with an element in the simulation +so that the simulation is aware of the source. + +```python + mesh.GetElement(0).SetFluidSource(source) +``` +We now generate the cells + +```python + cell_type = DifferentiatedCellProliferativeType() + cell_generator = CellsGenerator["UniformCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), cell_type) +``` +Then we set up the cell population + +```python + cell_population = ImmersedBoundaryCellPopulation[2](mesh, cells) +``` +Finally, we must tell the cell population that fluid sources are present. + +```python + cell_population.SetIfPopulationHasActiveSources(True) +``` +#### Varying the Source Location and Strength + **Practice** You can experiment with the source location. Try moving it + closer to and further away from the cells. + + **Practice** Try modifying the source strength to see what impact this + has on the cell shapes. + +Below, we visualize the cell population + +```python + scene = VtkScene[2]() + scene.SetCellPopulation(cell_population) + nb_manager = JupyterNotebookManager() + nb_manager.vtk_show(scene, height=300) +``` +Create a simulator to manage the simulation + +```python + simulator = OffLatticeSimulation[2, 2](cell_population) + simulator.SetNumericalMethod(ForwardEulerNumericalMethod[2, 2]()) + simulator.GetNumericalMethod().SetUseUpdateNodeLocation(True) +``` +Add an immersed boundary simulation modifier + +```python + ib_modifier = ImmersedBoundarySimulationModifier[2]() + simulator.AddSimulationModifier(ib_modifier) +``` +#### Fluid-Cell Interaction + **Practice** Try modifying the spring constant of the + `ImmersedBoundaryLinearMembraneForce` to see how this changes the + effect of the fluid source on the cells. + +```python + membrane_force = ImmersedBoundaryLinearMembraneForce[2]() + membrane_force.SetElementSpringConst(1.0 * 1e7) + ib_modifier.AddImmersedBoundaryForce(membrane_force) +``` +Add an inter-cellular force law + +```python + interaction_force = ImmersedBoundaryLinearInteractionForce[2]() + interaction_force.SetSpringConst(1.0 * 1e6) + ib_modifier.AddImmersedBoundaryForce(interaction_force) +``` +#### Adding More Sources + **Practice** Try adding a second fluid source. You will need to + use a unique index, and attach it to a different element as + each element can only manage a single fluid source. + +Next, we set the simulation properties + +```python + dt = 0.05 + simulator.SetOutputDirectory("Python/TestImmersedBoundary_3") + simulator.SetDt(dt) + simulator.SetSamplingTimestepMultiple(4) + simulator.SetEndTime(300 * dt) +``` +Finally, we run the simulation + +```python + simulator.Solve() +``` +Then we visualize the end state + +```python + nb_manager.vtk_show(scene, height=300) +``` +Reset the simulation environment in the notebook +JUPYTER_TEARDOWN + +#### Further Exercises + * Try integrating a different cell cycle model to introduce cell + division. See how the presence of a fluid source impacts the + structure that is formed. + * Use one of the cell writers to collect some statistics + +```python +if __name__ == "__main__": + unittest.main(verbosity=2) +``` + +## Full code + +```python +import unittest + +import chaste + +from chaste.cell_based import ( + AbstractCellBasedTestSuite, + CellsGenerator, + DifferentiatedCellProliferativeType, + ForwardEulerNumericalMethod, + ImmersedBoundaryCellPopulation, + ImmersedBoundaryLinearInteractionForce, + ImmersedBoundaryLinearMembraneForce, + ImmersedBoundarySimulationModifier, + OffLatticeSimulation, +) + +from chaste.mesh import ( + FluidSource, + ImmersedBoundaryPalisadeMeshGenerator, +) + +from chaste.visualization import ( + JupyterNotebookManager, + JupyterSceneModifier, + VtkScene, +) + +class TestPyImmersedBoundaryTutorial(AbstractCellBasedTestSuite): + + def test_simple_immersed_boundary_simulation(self): + + # JUPYTER_SETUP + + gen = ImmersedBoundaryPalisadeMeshGenerator(1, 128, 0.1, 2.0, 0.0, False) + mesh = gen.GetMesh() + + mesh.SetNumGridPtsXAndY(64) + + cell_type = DifferentiatedCellProliferativeType() + cell_generator = CellsGenerator["UniformCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), cell_type) + + cell_population = ImmersedBoundaryCellPopulation[2](mesh, cells) + cell_population.SetIfPopulationHasActiveSources(False) + + scene = VtkScene[2]() + scene.SetCellPopulation(cell_population) + nb_manager = JupyterNotebookManager() + nb_manager.vtk_show(scene, height=300) + + simulator = OffLatticeSimulation[2, 2](cell_population) + simulator.SetNumericalMethod(ForwardEulerNumericalMethod[2, 2]()) + simulator.GetNumericalMethod().SetUseUpdateNodeLocation(True) + + ib_modifier = ImmersedBoundarySimulationModifier[2]() + simulator.AddSimulationModifier(ib_modifier) + + membrane_force = ImmersedBoundaryLinearMembraneForce[2]() + membrane_force.SetElementSpringConst(1.0 * 1e7) + ib_modifier.AddImmersedBoundaryForce(membrane_force) + + dt = 0.05 + simulator.SetOutputDirectory("Python/TestImmersedBoundary_1") + simulator.SetDt(dt) + simulator.SetSamplingTimestepMultiple(4) + simulator.SetEndTime(1000 * dt) + + scene_modifier = JupyterSceneModifier[2](nb_manager) + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(1000) + simulator.AddSimulationModifier(scene_modifier) + + simulator.Solve() + + # JUPYTER_TEARDOWN + + def test_multicell_immersed_boundary_simulation(self): + + # JUPYTER_SETUP + + gen = ImmersedBoundaryPalisadeMeshGenerator(5, 128, 0.1, 2.0, 0.0, False) + + mesh = gen.GetMesh() + mesh.SetNumGridPtsXAndY(64) + + cell_type = DifferentiatedCellProliferativeType() + cell_generator = CellsGenerator["UniformCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), cell_type) + + cell_population = ImmersedBoundaryCellPopulation[2](mesh, cells) + cell_population.SetIfPopulationHasActiveSources(False) + + scene = VtkScene[2]() + scene.SetCellPopulation(cell_population) + nb_manager = JupyterNotebookManager() + nb_manager.vtk_show(scene, height=300) + + simulator = OffLatticeSimulation[2, 2](cell_population) + simulator.SetNumericalMethod(ForwardEulerNumericalMethod[2, 2]()) + simulator.GetNumericalMethod().SetUseUpdateNodeLocation(True) + + ib_modifier = ImmersedBoundarySimulationModifier[2]() + simulator.AddSimulationModifier(ib_modifier) + + membrane_force = ImmersedBoundaryLinearMembraneForce[2]() + membrane_force.SetElementSpringConst(1.0 * 1e7) + ib_modifier.AddImmersedBoundaryForce(membrane_force) + + interaction_force = ImmersedBoundaryLinearInteractionForce[2]() + interaction_force.SetSpringConst(1.0 * 1e6) + ib_modifier.AddImmersedBoundaryForce(interaction_force) + + dt = 0.05 + simulator.SetOutputDirectory("Python/TestImmersedBoundary_2") + simulator.SetDt(dt) + simulator.SetSamplingTimestepMultiple(4) + simulator.SetEndTime(1000 * dt) + + simulator.Solve() + + nb_manager.vtk_show(scene, height=300) + + # JUPYTER_TEARDOWN + + def test_fluid_source_immersed_boundary_simulation(self): + + # JUPYTER_SETUP + + source = FluidSource[2](0, 0.5, 0.7) + + source.SetStrength(0.012) + + gen = ImmersedBoundaryPalisadeMeshGenerator(5, 128, 0.1, 2.0, 0.0, False) + mesh = gen.GetMesh() + mesh.SetNumGridPtsXAndY(64) + + mesh.GetElement(0).SetFluidSource(source) + + cell_type = DifferentiatedCellProliferativeType() + cell_generator = CellsGenerator["UniformCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), cell_type) + + cell_population = ImmersedBoundaryCellPopulation[2](mesh, cells) + + cell_population.SetIfPopulationHasActiveSources(True) + + scene = VtkScene[2]() + scene.SetCellPopulation(cell_population) + nb_manager = JupyterNotebookManager() + nb_manager.vtk_show(scene, height=300) + + simulator = OffLatticeSimulation[2, 2](cell_population) + simulator.SetNumericalMethod(ForwardEulerNumericalMethod[2, 2]()) + simulator.GetNumericalMethod().SetUseUpdateNodeLocation(True) + + ib_modifier = ImmersedBoundarySimulationModifier[2]() + simulator.AddSimulationModifier(ib_modifier) + + membrane_force = ImmersedBoundaryLinearMembraneForce[2]() + membrane_force.SetElementSpringConst(1.0 * 1e7) + ib_modifier.AddImmersedBoundaryForce(membrane_force) + + interaction_force = ImmersedBoundaryLinearInteractionForce[2]() + interaction_force.SetSpringConst(1.0 * 1e6) + ib_modifier.AddImmersedBoundaryForce(interaction_force) + + dt = 0.05 + simulator.SetOutputDirectory("Python/TestImmersedBoundary_3") + simulator.SetDt(dt) + simulator.SetSamplingTimestepMultiple(4) + simulator.SetEndTime(300 * dt) + + simulator.Solve() + + nb_manager.vtk_show(scene, height=300) + +if __name__ == "__main__": + unittest.main(verbosity=2) +``` diff --git a/pychaste/src/py/doc/tutorial/MeshBasedCellSimulations.ipynb b/pychaste/src/py/doc/tutorial/MeshBasedCellSimulations.ipynb new file mode 100644 index 0000000000..c811bdb2d7 --- /dev/null +++ b/pychaste/src/py/doc/tutorial/MeshBasedCellSimulations.ipynb @@ -0,0 +1,555 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d64a9bc9", + "metadata": {}, + "source": [ + "This tutorial is automatically generated from [TestPyMeshBasedCellSimulationsTutorial.py](https://github.com/Chaste/Chaste/blob/develop/pychaste/test/tutorial/TestPyMeshBasedCellSimulationsTutorial.py) at revision [4045f91a83f5](https://github.com/Chaste/Chaste/commit/4045f91a83f55dc4a97f2ca4f97b0c32f4e43a4a)." + ] + }, + { + "cell_type": "markdown", + "id": "3e39f5f8", + "metadata": {}, + "source": [ + "\n", + "## Introduction\n", + "In this tutorial we show how Chaste can be used to create, run and visualize mesh-based simulations.\n", + "Full details of the mathematical model can be found in van Leeuwen et al. (2009) [doi:10.1111/j.1365-2184.2009.00627.x].\n", + "\n", + "## The Test\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bab1c61a", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt # Plotting\n", + "import numpy as np # Matrix tools\n", + "import chaste # The PyChaste module\n", + "import chaste.cell_based # Contains cell populations\n", + "import chaste.mesh # Contains meshes\n", + "import chaste.visualization # Visualization tools\n", + "from chaste.cell_based import AbstractCellBasedTestSuite" + ] + }, + { + "cell_type": "markdown", + "id": "c10e7078", + "metadata": {}, + "source": [ + "### Test 1 - a basic mesh-based simulation\n", + "In the first test, we run a simple mesh-based simulation,\n", + "in which we create a monolayer of cells, using a mutable mesh. Each cell is assigned a stochastic cell-cycle model.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2a28ec66", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.cell_based.SetupNotebookTest() # Set up the test" + ] + }, + { + "cell_type": "markdown", + "id": "828a27bf", + "metadata": {}, + "source": [ + "Next, we generate a mutable mesh. To create a `MutableMesh`, we can use the `HoneycombMeshGenerator`.\n", + "This generates a honeycomb-shaped mesh, in which all nodes are equidistant. Here the first and second arguments define the size of the mesh -\n", + "we have chosen a mesh that is 4 nodes (i.e. cells) wide, and 4 nodes high.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e1f7f1c0", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.core.OutputFileHandler(\"Python/TestMeshBasedCellSimulationsTutorial\")\n", + "generator = chaste.mesh.HoneycombMeshGenerator(4, 4)\n", + "mesh = generator.GetMesh()" + ] + }, + { + "cell_type": "markdown", + "id": "75eb348b", + "metadata": {}, + "source": [ + "Having created a mesh, we now create some cells. To do this, we use the `CellsGenerator` helper class,\n", + "which is specialized by the type of cell cycle model required (here `UniformCellCycleModel`) and the dimension.\n", + "For a list of possible cell cycle models see subclasses of `AbstractCellCycleModel`.\n", + "Note that some of these models will require information on the surrounding medium such as Oxygen concentration to work,\n", + "see specific class documentation for details. We create an empty vector of cells and pass this into the method along with the mesh.\n", + "The second argument represents the size of that the list of cells should become - one cell for each node,\n", + "the third argument specifies the proliferative type of the cell.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "396f670c", + "metadata": {}, + "outputs": [], + "source": [ + "transit_type = chaste.cell_based.TransitCellProliferativeType()\n", + "cell_generator = chaste.cell_based.CellsGenerator[\"UniformCellCycleModel\", 2]()\n", + "cells = cell_generator.GenerateBasicRandom(mesh.GetNumNodes(), transit_type)" + ] + }, + { + "cell_type": "markdown", + "id": "1de70cfb", + "metadata": {}, + "source": [ + "Now we have a mesh and a set of cells to go with it, we can create a `CellPopulation`.\n", + "In general, this class associates a collection of cells with a mesh. For this test, because we have a `MutableMesh`,\n", + "we use a particular type of cell population called a `MeshBasedCellPopulation`.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5f281abc", + "metadata": {}, + "outputs": [], + "source": [ + "cell_population = chaste.cell_based.MeshBasedCellPopulation[2, 2](mesh, cells)" + ] + }, + { + "cell_type": "markdown", + "id": "55881288", + "metadata": {}, + "source": [ + "To view the results of this and the next test in Paraview it is necessary to explicitly\n", + "generate the required .vtu files.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0f148419", + "metadata": {}, + "outputs": [], + "source": [ + "cell_population.AddPopulationWriterVoronoiDataWriter()" + ] + }, + { + "cell_type": "markdown", + "id": "475f50bd", + "metadata": {}, + "source": [ + "We can set up a `VtkScene` to do a quick visualization of the population before running the analysis.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4644ce15", + "metadata": {}, + "outputs": [], + "source": [ + "scene = chaste.visualization.VtkScene[2]()\n", + "scene.SetCellPopulation(cell_population)\n", + "nb_manager = chaste.visualization.JupyterNotebookManager()\n", + "nb_manager.vtk_show(scene, height=600)" + ] + }, + { + "cell_type": "markdown", + "id": "32735ff0", + "metadata": {}, + "source": [ + "We then pass in the cell population into an `OffLatticeSimulation`, and set the output directory and end time.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "700674f9", + "metadata": {}, + "outputs": [], + "source": [ + "simulator = chaste.cell_based.OffLatticeSimulation[2, 2](cell_population)\n", + "simulator.SetOutputDirectory(\"Python/TestMeshBasedCellSimulationsTutorial\")\n", + "simulator.SetEndTime(10.0)" + ] + }, + { + "cell_type": "markdown", + "id": "4c555b2f", + "metadata": {}, + "source": [ + "For longer simulations, we may not want to output the results every time step. In this case we can use the following method,\n", + "to print results every 12 time steps instead. As the default time step used by the simulator is 30 seconds,\n", + "this method will cause the simulator to print results every 6 minutes (or 0.1 hours).\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "09529b20", + "metadata": {}, + "outputs": [], + "source": [ + "simulator.SetSamplingTimestepMultiple(12)" + ] + }, + { + "cell_type": "markdown", + "id": "d9d58b99", + "metadata": {}, + "source": [ + "We must now create one or more force laws, which determine the mechanics of the centres of each cell in a cell population.\n", + "For this test, we use one force law, based on the spring based model, and pass it to the `OffLatticeSimulation`.\n", + "For a list of possible forces see subclasses of `AbstractForce`. Note that some of these forces are not compatible with mesh-based simulations,\n", + "see the specific class documentation for details. If you try to use an incompatible class then you will receive a warning.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0afb0adb", + "metadata": {}, + "outputs": [], + "source": [ + "force = chaste.cell_based.GeneralisedLinearSpringForce[2, 2]()\n", + "simulator.AddForce(force)" + ] + }, + { + "cell_type": "markdown", + "id": "8a082dda", + "metadata": {}, + "source": [ + "Save snapshot images of the population during the simulation\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "73b02527", + "metadata": {}, + "outputs": [], + "source": [ + "scene_modifier = chaste.cell_based.VtkSceneModifier[2]()\n", + "scene_modifier.SetVtkScene(scene)\n", + "scene_modifier.SetUpdateFrequency(100)\n", + "simulator.AddSimulationModifier(scene_modifier)" + ] + }, + { + "cell_type": "markdown", + "id": "6c2553d3", + "metadata": {}, + "source": [ + "To run the simulation, we call `Solve()`. We can again do a quick rendering of the population at the end of the simulation\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "74243340", + "metadata": {}, + "outputs": [], + "source": [ + "scene.Start()\n", + "simulator.Solve()\n", + "scene.End()\n", + "chaste.cell_based.TearDownNotebookTest() # Tear down the test" + ] + }, + { + "cell_type": "markdown", + "id": "81933629", + "metadata": {}, + "source": [ + "Full results can be visualized in Paraview from the `file_handler.GetOutputDirectoryFullPath()` directory.\n", + "\n", + "### Test 2 - a basic mesh-based simulation with ghost nodes\n", + "In the second test, we run a simple mesh-based simulation with ghost nodes, in which we create a monolayer of cells, using a mutable mesh.\n", + "Each cell is assigned a stochastic cell-cycle model.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a34953b2", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.cell_based.SetupNotebookTest() # Set up the test" + ] + }, + { + "cell_type": "markdown", + "id": "97777b3a", + "metadata": {}, + "source": [ + "We start by generating a mutable mesh. To create a `MutableMesh`, we can use the `HoneycombMeshGenerator` as before.\n", + "Here the first and second arguments define the size of the mesh - we have chosen a mesh that is 2 nodes (i.e. cells) wide,\n", + "and 2 nodes high. The third argument specifies the number of layers of ghost nodes to make.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "aa8691e2", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.core.OutputFileHandler(\"Python/TestMeshBasedCellPopulationWithGhostNodes\")\n", + "generator = chaste.mesh.HoneycombMeshGenerator(5, 5, 2)\n", + "mesh = generator.GetMesh()" + ] + }, + { + "cell_type": "markdown", + "id": "7fcb9cbe", + "metadata": {}, + "source": [ + "We only want to create cells to attach to real nodes, so we use the method `GetCellLocationIndices` to get the\n", + "indices of the real nodes in the mesh. This will be passed in to the cell population later on.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7d013ca0", + "metadata": {}, + "outputs": [], + "source": [ + "locs = generator.GetCellLocationIndices()" + ] + }, + { + "cell_type": "markdown", + "id": "47dd22f8", + "metadata": {}, + "source": [ + "Having created a mesh, we now create some cells. To do this, we use the `CellsGenerator` helper class again.\n", + "This time the second argument is different and is the number of real nodes in the mesh.\n", + "As before all cells have `TransitCellProliferativeType`.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b6a564b8", + "metadata": {}, + "outputs": [], + "source": [ + "transit_type = chaste.cell_based.TransitCellProliferativeType()\n", + "cell_generator = chaste.cell_based.CellsGenerator[\"UniformCellCycleModel\", 2]()\n", + "cells = cell_generator.GenerateBasicRandom(len(locs), transit_type)" + ] + }, + { + "cell_type": "markdown", + "id": "e4dbfec7", + "metadata": {}, + "source": [ + "Now we have a mesh and a set of cells to go with it, we can create a `CellPopulation`.\n", + "In general, this class associates a collection of cells with a set of elements or a mesh.\n", + "For this test, because we have a `MutableMesh`, and ghost nodes we use a particular type of cell population called\n", + "a `MeshBasedCellPopulationWithGhostNodes`. The third argument of the constructor takes a vector of the indices of the real nodes\n", + "and should be the same length as the vector of cell pointers.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "581b2832", + "metadata": {}, + "outputs": [], + "source": [ + "cell_population = chaste.cell_based.MeshBasedCellPopulationWithGhostNodes[2](mesh, cells, locs)" + ] + }, + { + "cell_type": "markdown", + "id": "b5aa310a", + "metadata": {}, + "source": [ + "Again Paraview output is explicitly requested.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16edf4a0", + "metadata": {}, + "outputs": [], + "source": [ + "cell_population.AddPopulationWriterVoronoiDataWriter()" + ] + }, + { + "cell_type": "markdown", + "id": "51118a75", + "metadata": {}, + "source": [ + "We can set up a `VtkScene` to do a quick visualization of the population before running the analysis.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "92c3a819", + "metadata": {}, + "outputs": [], + "source": [ + "scene = chaste.visualization.VtkScene[2]()\n", + "scene.SetCellPopulation(cell_population)\n", + "scene.GetCellPopulationActorGenerator().SetShowVoronoiMeshEdges(True)\n", + "nb_manager.vtk_show(scene, height=600)" + ] + }, + { + "cell_type": "markdown", + "id": "cd3e8390", + "metadata": {}, + "source": [ + "We then pass in the cell population into an `OffLatticeSimulation`, and set the output directory, output multiple and end time.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4887bf6a", + "metadata": {}, + "outputs": [], + "source": [ + "simulator = chaste.cell_based.OffLatticeSimulation[2, 2](cell_population)\n", + "simulator.SetOutputDirectory(\"Python/TestMeshBasedCellPopulationWithGhostNodes\")\n", + "simulator.SetEndTime(10.0)\n", + "simulator.SetSamplingTimestepMultiple(12)" + ] + }, + { + "cell_type": "markdown", + "id": "da8fe87d", + "metadata": {}, + "source": [ + "Save snapshot images of the population during the simulation\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4147e86f", + "metadata": {}, + "outputs": [], + "source": [ + "scene_modifier = chaste.cell_based.VtkSceneModifier[2]()\n", + "scene_modifier.SetVtkScene(scene)\n", + "scene_modifier.SetUpdateFrequency(300)\n", + "simulator.AddSimulationModifier(scene_modifier)" + ] + }, + { + "cell_type": "markdown", + "id": "3b88459f", + "metadata": {}, + "source": [ + "Again we create a force law, and pass it to the `OffLatticeSimulation`.\n", + "This force law ensures that ghost nodes don't exert forces on real nodes but real nodes exert forces on ghost nodes.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "78897c7b", + "metadata": {}, + "outputs": [], + "source": [ + "force = chaste.cell_based.GeneralisedLinearSpringForce[2, 2]()\n", + "simulator.AddForce(force)" + ] + }, + { + "cell_type": "markdown", + "id": "591e001f", + "metadata": {}, + "source": [ + "To run the simulation, we call `Solve()`.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0e8fa2df", + "metadata": {}, + "outputs": [], + "source": [ + "scene.Start()\n", + "simulator.Solve()" + ] + }, + { + "cell_type": "markdown", + "id": "eb204b1e", + "metadata": {}, + "source": [ + "The next two lines are for test purposes only and are not part of this tutorial.\n", + "If different simulation input parameters are being explored the lines should be removed.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2cb543f5", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.cell_based.TearDownNotebookTest() # Tear down the test" + ] + }, + { + "cell_type": "markdown", + "id": "b8d6bc68", + "metadata": {}, + "source": [ + "Full results can be visualized in Paraview from the `file_handler.GetOutputDirectoryFullPath()` directory.\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pychaste/src/py/doc/tutorial/MeshBasedCellSimulations.md b/pychaste/src/py/doc/tutorial/MeshBasedCellSimulations.md new file mode 100644 index 0000000000..6280e10c73 --- /dev/null +++ b/pychaste/src/py/doc/tutorial/MeshBasedCellSimulations.md @@ -0,0 +1,332 @@ +--- +title : "Mesh Based Cell Simulations" +summary: "" +draft: false +images: [] +toc: true +layout: "single" +--- +This tutorial is automatically generated from [TestPyMeshBasedCellSimulationsTutorial.py](https://github.com/Chaste/Chaste/blob/develop/pychaste/test/tutorial/TestPyMeshBasedCellSimulationsTutorial.py) at revision [4045f91a83f5](https://github.com/Chaste/Chaste/commit/4045f91a83f55dc4a97f2ca4f97b0c32f4e43a4a). + +Note that the code is given in full at the bottom of the page. + +## Introduction +In this tutorial we show how Chaste can be used to create, run and visualize mesh-based simulations. +Full details of the mathematical model can be found in van Leeuwen et al. (2009) [doi:10.1111/j.1365-2184.2009.00627.x]. + +## The Test + +```python +import unittest # Python testing framework + +import matplotlib.pyplot as plt # Plotting +import numpy as np # Matrix tools + +import chaste # The PyChaste module +import chaste.cell_based # Contains cell populations +import chaste.mesh # Contains meshes +import chaste.visualization # Visualization tools + +from chaste.cell_based import AbstractCellBasedTestSuite + +class TestPyMeshBasedCellSimulationsTutorial(AbstractCellBasedTestSuite): +``` +### Test 1 - a basic mesh-based simulation +In the first test, we run a simple mesh-based simulation, +in which we create a monolayer of cells, using a mutable mesh. Each cell is assigned a stochastic cell-cycle model. + +```python + def test_monolayer(self): + + # JUPYTER_SETUP +``` +Next, we generate a mutable mesh. To create a `MutableMesh`, we can use the `HoneycombMeshGenerator`. +This generates a honeycomb-shaped mesh, in which all nodes are equidistant. Here the first and second arguments define the size of the mesh - +we have chosen a mesh that is 4 nodes (i.e. cells) wide, and 4 nodes high. + +```python + chaste.core.OutputFileHandler("Python/TestMeshBasedCellSimulationsTutorial") + generator = chaste.mesh.HoneycombMeshGenerator(4, 4) + mesh = generator.GetMesh() +``` +Having created a mesh, we now create some cells. To do this, we use the `CellsGenerator` helper class, +which is specialized by the type of cell cycle model required (here `UniformCellCycleModel`) and the dimension. +For a list of possible cell cycle models see subclasses of `AbstractCellCycleModel`. +Note that some of these models will require information on the surrounding medium such as Oxygen concentration to work, +see specific class documentation for details. We create an empty vector of cells and pass this into the method along with the mesh. +The second argument represents the size of that the list of cells should become - one cell for each node, +the third argument specifies the proliferative type of the cell. + +```python + transit_type = chaste.cell_based.TransitCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumNodes(), transit_type) +``` +Now we have a mesh and a set of cells to go with it, we can create a `CellPopulation`. +In general, this class associates a collection of cells with a mesh. For this test, because we have a `MutableMesh`, +we use a particular type of cell population called a `MeshBasedCellPopulation`. + +```python + cell_population = chaste.cell_based.MeshBasedCellPopulation[2, 2](mesh, cells) +``` +To view the results of this and the next test in Paraview it is necessary to explicitly +generate the required .vtu files. + +```python + cell_population.AddPopulationWriterVoronoiDataWriter() +``` +We can set up a `VtkScene` to do a quick visualization of the population before running the analysis. + +```python + scene = chaste.visualization.VtkScene[2]() + scene.SetCellPopulation(cell_population) + # JUPYTER_SHOW_FIRST + scene.Start() # JUPYTER_SHOW +``` +We then pass in the cell population into an `OffLatticeSimulation`, and set the output directory and end time. + +```python + simulator = chaste.cell_based.OffLatticeSimulation[2, 2](cell_population) + simulator.SetOutputDirectory("Python/TestMeshBasedCellSimulationsTutorial") + simulator.SetEndTime(10.0) +``` +For longer simulations, we may not want to output the results every time step. In this case we can use the following method, +to print results every 12 time steps instead. As the default time step used by the simulator is 30 seconds, +this method will cause the simulator to print results every 6 minutes (or 0.1 hours). + +```python + simulator.SetSamplingTimestepMultiple(12) +``` +We must now create one or more force laws, which determine the mechanics of the centres of each cell in a cell population. +For this test, we use one force law, based on the spring based model, and pass it to the `OffLatticeSimulation`. +For a list of possible forces see subclasses of `AbstractForce`. Note that some of these forces are not compatible with mesh-based simulations, +see the specific class documentation for details. If you try to use an incompatible class then you will receive a warning. + +```python + force = chaste.cell_based.GeneralisedLinearSpringForce[2, 2]() + simulator.AddForce(force) +``` +Save snapshot images of the population during the simulation + +```python + scene_modifier = chaste.cell_based.VtkSceneModifier[2]() + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(100) + simulator.AddSimulationModifier(scene_modifier) +``` +To run the simulation, we call `Solve()`. We can again do a quick rendering of the population at the end of the simulation + +```python + scene.Start() + simulator.Solve() + scene.End() + + # JUPYTER_TEARDOWN +``` +Full results can be visualized in Paraview from the `file_handler.GetOutputDirectoryFullPath()` directory. + +### Test 2 - a basic mesh-based simulation with ghost nodes +In the second test, we run a simple mesh-based simulation with ghost nodes, in which we create a monolayer of cells, using a mutable mesh. +Each cell is assigned a stochastic cell-cycle model. + +```python + def test_monolayer_with_ghost_nodes(self): + + # JUPYTER_SETUP +``` +We start by generating a mutable mesh. To create a `MutableMesh`, we can use the `HoneycombMeshGenerator` as before. +Here the first and second arguments define the size of the mesh - we have chosen a mesh that is 2 nodes (i.e. cells) wide, +and 2 nodes high. The third argument specifies the number of layers of ghost nodes to make. + +```python + chaste.core.OutputFileHandler("Python/TestMeshBasedCellPopulationWithGhostNodes") + generator = chaste.mesh.HoneycombMeshGenerator(5, 5, 2) + mesh = generator.GetMesh() +``` +We only want to create cells to attach to real nodes, so we use the method `GetCellLocationIndices` to get the +indices of the real nodes in the mesh. This will be passed in to the cell population later on. + +```python + locs = generator.GetCellLocationIndices() +``` +Having created a mesh, we now create some cells. To do this, we use the `CellsGenerator` helper class again. +This time the second argument is different and is the number of real nodes in the mesh. +As before all cells have `TransitCellProliferativeType`. + +```python + transit_type = chaste.cell_based.TransitCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(len(locs), transit_type) +``` +Now we have a mesh and a set of cells to go with it, we can create a `CellPopulation`. +In general, this class associates a collection of cells with a set of elements or a mesh. +For this test, because we have a `MutableMesh`, and ghost nodes we use a particular type of cell population called +a `MeshBasedCellPopulationWithGhostNodes`. The third argument of the constructor takes a vector of the indices of the real nodes +and should be the same length as the vector of cell pointers. + +```python + cell_population = chaste.cell_based.MeshBasedCellPopulationWithGhostNodes[2](mesh, cells, locs) +``` +Again Paraview output is explicitly requested. + +```python + cell_population.AddPopulationWriterVoronoiDataWriter() +``` +We can set up a `VtkScene` to do a quick visualization of the population before running the analysis. + +```python + scene = chaste.visualization.VtkScene[2]() + scene.SetCellPopulation(cell_population) + scene.GetCellPopulationActorGenerator().SetShowVoronoiMeshEdges(True) + # JUPYTER_SHOW +``` +We then pass in the cell population into an `OffLatticeSimulation`, and set the output directory, output multiple and end time. + +```python + simulator = chaste.cell_based.OffLatticeSimulation[2, 2](cell_population) + simulator.SetOutputDirectory("Python/TestMeshBasedCellPopulationWithGhostNodes") + simulator.SetEndTime(10.0) + simulator.SetSamplingTimestepMultiple(12) +``` +Save snapshot images of the population during the simulation + +```python + scene_modifier = chaste.cell_based.VtkSceneModifier[2]() + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(300) + simulator.AddSimulationModifier(scene_modifier) +``` +Again we create a force law, and pass it to the `OffLatticeSimulation`. +This force law ensures that ghost nodes don't exert forces on real nodes but real nodes exert forces on ghost nodes. + +```python + force = chaste.cell_based.GeneralisedLinearSpringForce[2, 2]() + simulator.AddForce(force) +``` +To run the simulation, we call `Solve()`. + +```python + scene.Start() + simulator.Solve() +``` +The next two lines are for test purposes only and are not part of this tutorial. +If different simulation input parameters are being explored the lines should be removed. + +```python + self.assertEqual(cell_population.GetNumRealCells(), 48) + self.assertAlmostEqual(chaste.cell_based.SimulationTime.Instance().GetTime(), 10.0, 6) + + # JUPYTER_TEARDOWN +``` +Full results can be visualized in Paraview from the `file_handler.GetOutputDirectoryFullPath()` directory. + +```python +if __name__ == "__main__": + unittest.main(verbosity=2) +``` + +## Full code + +```python +import unittest # Python testing framework + +import matplotlib.pyplot as plt # Plotting +import numpy as np # Matrix tools + +import chaste # The PyChaste module +import chaste.cell_based # Contains cell populations +import chaste.mesh # Contains meshes +import chaste.visualization # Visualization tools + +from chaste.cell_based import AbstractCellBasedTestSuite + +class TestPyMeshBasedCellSimulationsTutorial(AbstractCellBasedTestSuite): + + def test_monolayer(self): + + # JUPYTER_SETUP + + chaste.core.OutputFileHandler("Python/TestMeshBasedCellSimulationsTutorial") + generator = chaste.mesh.HoneycombMeshGenerator(4, 4) + mesh = generator.GetMesh() + + transit_type = chaste.cell_based.TransitCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumNodes(), transit_type) + + cell_population = chaste.cell_based.MeshBasedCellPopulation[2, 2](mesh, cells) + + cell_population.AddPopulationWriterVoronoiDataWriter() + + scene = chaste.visualization.VtkScene[2]() + scene.SetCellPopulation(cell_population) + # JUPYTER_SHOW_FIRST + scene.Start() # JUPYTER_SHOW + + simulator = chaste.cell_based.OffLatticeSimulation[2, 2](cell_population) + simulator.SetOutputDirectory("Python/TestMeshBasedCellSimulationsTutorial") + simulator.SetEndTime(10.0) + + simulator.SetSamplingTimestepMultiple(12) + + force = chaste.cell_based.GeneralisedLinearSpringForce[2, 2]() + simulator.AddForce(force) + + scene_modifier = chaste.cell_based.VtkSceneModifier[2]() + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(100) + simulator.AddSimulationModifier(scene_modifier) + + scene.Start() + simulator.Solve() + scene.End() + + # JUPYTER_TEARDOWN + + def test_monolayer_with_ghost_nodes(self): + + # JUPYTER_SETUP + + chaste.core.OutputFileHandler("Python/TestMeshBasedCellPopulationWithGhostNodes") + generator = chaste.mesh.HoneycombMeshGenerator(5, 5, 2) + mesh = generator.GetMesh() + + locs = generator.GetCellLocationIndices() + + transit_type = chaste.cell_based.TransitCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(len(locs), transit_type) + + cell_population = chaste.cell_based.MeshBasedCellPopulationWithGhostNodes[2](mesh, cells, locs) + + cell_population.AddPopulationWriterVoronoiDataWriter() + + scene = chaste.visualization.VtkScene[2]() + scene.SetCellPopulation(cell_population) + scene.GetCellPopulationActorGenerator().SetShowVoronoiMeshEdges(True) + # JUPYTER_SHOW + + simulator = chaste.cell_based.OffLatticeSimulation[2, 2](cell_population) + simulator.SetOutputDirectory("Python/TestMeshBasedCellPopulationWithGhostNodes") + simulator.SetEndTime(10.0) + simulator.SetSamplingTimestepMultiple(12) + + scene_modifier = chaste.cell_based.VtkSceneModifier[2]() + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(300) + simulator.AddSimulationModifier(scene_modifier) + + force = chaste.cell_based.GeneralisedLinearSpringForce[2, 2]() + simulator.AddForce(force) + + scene.Start() + simulator.Solve() + + self.assertEqual(cell_population.GetNumRealCells(), 48) + self.assertAlmostEqual(chaste.cell_based.SimulationTime.Instance().GetTime(), 10.0, 6) + + # JUPYTER_TEARDOWN + +if __name__ == "__main__": + unittest.main(verbosity=2) +``` diff --git a/pychaste/src/py/doc/tutorial/NodeBasedCellSimulations.ipynb b/pychaste/src/py/doc/tutorial/NodeBasedCellSimulations.ipynb new file mode 100644 index 0000000000..0cb2cd9f91 --- /dev/null +++ b/pychaste/src/py/doc/tutorial/NodeBasedCellSimulations.ipynb @@ -0,0 +1,761 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "3c3a5941", + "metadata": {}, + "source": [ + "This tutorial is automatically generated from [TestPyNodeBasedCellSimulationsTutorial.py](https://github.com/Chaste/Chaste/blob/develop/pychaste/test/tutorial/TestPyNodeBasedCellSimulationsTutorial.py) at revision [4045f91a83f5](https://github.com/Chaste/Chaste/commit/4045f91a83f55dc4a97f2ca4f97b0c32f4e43a4a)." + ] + }, + { + "cell_type": "markdown", + "id": "a703699f", + "metadata": {}, + "source": [ + "\n", + "## Introduction\n", + "In this tutorial we show how Chaste can be used to create, run and visualize node-based simulations. Full details of the mechanical model can be found in Pathamathan et\n", + "al \"A computational study of discrete mechanical tissue models\", Physical Biology. Vol. 6. No. 3. 2009.. DOI (10.1088/1478-3975/6/3/036001).\n", + "\n", + "## The Test\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f6325d50", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np # Matrix tools\n", + "import chaste # The PyChaste module\n", + "import chaste.cell_based # Contains cell populations\n", + "import chaste.mesh # Contains meshes\n", + "import chaste.visualization # Visualization tools\n", + "from chaste.cell_based import AbstractCellBasedTestSuite" + ] + }, + { + "cell_type": "markdown", + "id": "7de0fe9d", + "metadata": {}, + "source": [ + "### Test 1 - A basic node-based simulation\n", + "In the first test, we run a simple node-based simulation, in which we create a monolayer of cells,\n", + "using a nodes only mesh. Each cell is assigned a uniform cell-cycle model.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3b76d85d", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.cell_based.SetupNotebookTest() # Set up the test" + ] + }, + { + "cell_type": "markdown", + "id": "0919947c", + "metadata": {}, + "source": [ + "The first thing we do is generate a nodes only mesh. To do this we first create a `MutableMesh` to use as a generating mesh.\n", + "To do this we can use the `HoneycombMeshGenerator`. This generates a honeycomb-shaped mesh, in which all nodes are equidistant.\n", + "Here the first and second arguments define the size of the mesh - we have chosen a mesh that is 2 nodes (i.e. cells) wide, and 2 nodes high.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "44223a79", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.core.OutputFileHandler(\"Python/TestNodeBasedCellSimulationsTutorial\")\n", + "generator = chaste.mesh.HoneycombMeshGenerator(2, 2)\n", + "generating_mesh = generator.GetMesh()" + ] + }, + { + "cell_type": "markdown", + "id": "3ab55d3d", + "metadata": {}, + "source": [ + "Once we have a MutableMesh we can generate a NodesOnlyMesh from it using the following commands.\n", + "Note you can also generate the NodesOnlyMesh from a collection of nodes.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "814e2ef2", + "metadata": {}, + "outputs": [], + "source": [ + "mesh = chaste.mesh.NodesOnlyMesh[2]()" + ] + }, + { + "cell_type": "markdown", + "id": "4d823ceb", + "metadata": {}, + "source": [ + "To run node-based simulations you need to define a cut off length (second argument in `ConstructNodesWithoutMesh`),\n", + "which defines the connectivity of the nodes by defining a radius of interaction.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "32008ac0", + "metadata": {}, + "outputs": [], + "source": [ + "mesh.ConstructNodesWithoutMesh(generating_mesh, 1.5)" + ] + }, + { + "cell_type": "markdown", + "id": "f754b268", + "metadata": {}, + "source": [ + "Having created a mesh, we now create a (wrapped) vector of CellPtrs. To do this, we use the `CellsGenerator` helper class,\n", + "which is specialized for the type of cell model required (here `UniformCellCycleModel`) and the dimension.\n", + "We create an empty vector of cells and pass this into the method along with the mesh.\n", + "The second argument represents the size of that the vector cells should become - one cell for each node,\n", + "the third argument specifies the proliferative type of the cell.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2f8626a2", + "metadata": {}, + "outputs": [], + "source": [ + "transit_type = chaste.cell_based.TransitCellProliferativeType()\n", + "cell_generator = chaste.cell_based.CellsGenerator[\"UniformCellCycleModel\", 2]()\n", + "cells = cell_generator.GenerateBasicRandom(mesh.GetNumNodes(), transit_type)" + ] + }, + { + "cell_type": "markdown", + "id": "d1ae4c6f", + "metadata": {}, + "source": [ + "Now we have a mesh and a set of cells to go with it, we can create a `CellPopulation`.\n", + "In general, this class associates a collection of cells with a mesh. For this test,\n", + "because we have a `NodesOnlyMesh`, we use a particular type of cell population called a `NodeBasedCellPopulation`.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3f1c2480", + "metadata": {}, + "outputs": [], + "source": [ + "cell_population = chaste.cell_based.NodeBasedCellPopulation[2](mesh, cells)" + ] + }, + { + "cell_type": "markdown", + "id": "190fc9da", + "metadata": {}, + "source": [ + "We can set up a `VtkScene` to do a quick visualization of the population before running the analysis.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0919064a", + "metadata": {}, + "outputs": [], + "source": [ + "scene = chaste.visualization.VtkScene[2]()\n", + "scene.SetCellPopulation(cell_population)\n", + "nb_manager = chaste.visualization.JupyterNotebookManager()\n", + "nb_manager.vtk_show(scene, height=600)" + ] + }, + { + "cell_type": "markdown", + "id": "56380050", + "metadata": {}, + "source": [ + "We then pass in the cell population into an `OffLatticeSimulation`, and set the output directory, output multiple and end time\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "da272148", + "metadata": {}, + "outputs": [], + "source": [ + "simulator = chaste.cell_based.OffLatticeSimulation[2, 2](cell_population)\n", + "simulator.SetOutputDirectory(\"Python/TestNodeBasedCellSimulationsTutorial\")\n", + "simulator.SetSamplingTimestepMultiple(100)\n", + "simulator.SetEndTime(10.0)" + ] + }, + { + "cell_type": "markdown", + "id": "639c268e", + "metadata": {}, + "source": [ + "We now pass a force law to the simulation.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "96b94130", + "metadata": {}, + "outputs": [], + "source": [ + "force = chaste.cell_based.GeneralisedLinearSpringForce[2, 2]()\n", + "simulator.AddForce(force)" + ] + }, + { + "cell_type": "markdown", + "id": "b4d84017", + "metadata": {}, + "source": [ + "Save snapshot images of the population during the simulation\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8e8cb448", + "metadata": {}, + "outputs": [], + "source": [ + "scene_modifier = chaste.cell_based.VtkSceneModifier[2]()\n", + "scene_modifier.SetVtkScene(scene)\n", + "scene_modifier.SetUpdateFrequency(100)\n", + "simulator.AddSimulationModifier(scene_modifier)" + ] + }, + { + "cell_type": "markdown", + "id": "0875c6b7", + "metadata": {}, + "source": [ + "To run the simulation, we call `Solve()`. We can again do a quick rendering of the population at the end of the simulation\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1397db84", + "metadata": {}, + "outputs": [], + "source": [ + "scene.Start()\n", + "simulator.Solve()\n", + "scene.End()" + ] + }, + { + "cell_type": "markdown", + "id": "97b5453c", + "metadata": {}, + "source": [ + "The next two lines are for test purposes only and are not part of this tutorial.\n", + "If different simulation input parameters are being explored the lines should be removed.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "235b96e8", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.cell_based.TearDownNotebookTest() # Tear down the test" + ] + }, + { + "cell_type": "markdown", + "id": "898fd365", + "metadata": {}, + "source": [ + "### Test 2 - a basic node-based simulation in 3D\n", + "In the second test we run a simple node-based simulation in 3D. This is very similar to the 2D test with the dimension changed from 2 to 3 and\n", + "instead of using a mesh generator we generate the nodes directly.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3cd94e9a", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.cell_based.SetupNotebookTest() # Set up the test" + ] + }, + { + "cell_type": "markdown", + "id": "26ad786d", + "metadata": {}, + "source": [ + "First, we generate a nodes only mesh. This time we specify the nodes manually by first creating a vector of nodes\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3bfae388", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.core.OutputFileHandler(\n", + " \"Python/TestNodeBasedCellSimulationsSpheroidTutorial\"\n", + ")\n", + "nodes = []\n", + "nodes.append(chaste.mesh.Node[3](0, False, 0.5, 0.0, 0.0))\n", + "nodes.append(chaste.mesh.Node[3](1, False, -0.5, 0.0, 0.0))\n", + "nodes.append(chaste.mesh.Node[3](2, False, 0.0, 0.5, 0.0))\n", + "nodes.append(chaste.mesh.Node[3](3, False, 0.0, -0.5, 0.0))" + ] + }, + { + "cell_type": "markdown", + "id": "06b77b60", + "metadata": {}, + "source": [ + "Finally a NodesOnlyMesh is created and the vector of nodes is passed to the ConstructNodesWithoutMesh method.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d0d9d5ec", + "metadata": {}, + "outputs": [], + "source": [ + "mesh = chaste.mesh.NodesOnlyMesh[3]()" + ] + }, + { + "cell_type": "markdown", + "id": "cb3823bd", + "metadata": {}, + "source": [ + "To run node-based simulations you need to define a cut off length (second argument in ConstructNodesWithoutMesh),\n", + "which defines the connectivity of the nodes by defining a radius of interaction.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8bc520a4", + "metadata": {}, + "outputs": [], + "source": [ + "mesh.ConstructNodesWithoutMesh(nodes, 1.5)" + ] + }, + { + "cell_type": "markdown", + "id": "7aa1f61a", + "metadata": {}, + "source": [ + "Having created a mesh, we now create a std::vector of CellPtrs.\n", + "As before, we do this with the CellsGenerator helper class (this time with dimension 3).\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "96903cf4", + "metadata": {}, + "outputs": [], + "source": [ + "transit_type = chaste.cell_based.TransitCellProliferativeType()\n", + "cell_generator = chaste.cell_based.CellsGenerator[\"UniformCellCycleModel\", 3]()\n", + "cells = cell_generator.GenerateBasicRandom(mesh.GetNumNodes(), transit_type)" + ] + }, + { + "cell_type": "markdown", + "id": "852914f9", + "metadata": {}, + "source": [ + "Now we have a mesh and a set of cells to go with it, we can create a `CellPopulation`.\n", + "In general, this class associates a collection of cells with a mesh. For this test,\n", + "because we have a `NodesOnlyMesh`, we use a particular type of cell population called a `NodeBasedCellPopulation`.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4f7ffb29", + "metadata": {}, + "outputs": [], + "source": [ + "cell_population = chaste.cell_based.NodeBasedCellPopulation[3](mesh, cells)" + ] + }, + { + "cell_type": "markdown", + "id": "767b65ae", + "metadata": {}, + "source": [ + "We can set up a `VtkScene` to do a quick visualization of the population before running the analysis.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c8387b99", + "metadata": {}, + "outputs": [], + "source": [ + "scene = chaste.visualization.VtkScene[3]()\n", + "scene.SetCellPopulation(cell_population)\n", + "nb_manager.vtk_show(scene, height=600)" + ] + }, + { + "cell_type": "markdown", + "id": "31e14903", + "metadata": {}, + "source": [ + "We then pass in the cell population into an `OffLatticeSimulation`, and set the output directory, output multiple and end time\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1876f1f5", + "metadata": {}, + "outputs": [], + "source": [ + "simulator = chaste.cell_based.OffLatticeSimulation[3, 3](cell_population)\n", + "simulator.SetOutputDirectory(\"Python/TestNodeBasedCellSimulationsSpheroidTutorial\")\n", + "simulator.SetSamplingTimestepMultiple(12)\n", + "simulator.SetEndTime(10.0)" + ] + }, + { + "cell_type": "markdown", + "id": "526fa8d0", + "metadata": {}, + "source": [ + "We now pass a force law to the simulation.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "925caadc", + "metadata": {}, + "outputs": [], + "source": [ + "force = chaste.cell_based.GeneralisedLinearSpringForce[3, 3]()\n", + "simulator.AddForce(force)" + ] + }, + { + "cell_type": "markdown", + "id": "38759e3b", + "metadata": {}, + "source": [ + "Save snapshot images of the population during the simulation\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b7ae2ce1", + "metadata": {}, + "outputs": [], + "source": [ + "scene_modifier = chaste.cell_based.VtkSceneModifier[3]()\n", + "scene_modifier.SetVtkScene(scene)\n", + "scene_modifier.SetUpdateFrequency(100)\n", + "simulator.AddSimulationModifier(scene_modifier)" + ] + }, + { + "cell_type": "markdown", + "id": "ed10e9ec", + "metadata": {}, + "source": [ + "To run the simulation, we call `Solve()`. We can again do a quick rendering of the population at the end of the simulation\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "92b58b9c", + "metadata": {}, + "outputs": [], + "source": [ + "scene.Start()\n", + "simulator.Solve()\n", + "scene.End()" + ] + }, + { + "cell_type": "markdown", + "id": "bfcb1a6e", + "metadata": {}, + "source": [ + "The next two lines are for test purposes only and are not part of this tutorial.\n", + "If different simulation input parameters are being explored the lines should be removed.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3c311c16", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.cell_based.TearDownNotebookTest() # Tear down the test" + ] + }, + { + "cell_type": "markdown", + "id": "00d695c4", + "metadata": {}, + "source": [ + "### Test 3 - a node-based simulation on a restricted geometry\n", + "In the second test we run a simple node-based simulation in 3D. This is very similar to the 2D test with the dimension changed from 2 to 3 and\n", + "instead of using a mesh generator we generate the nodes directly.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fd3e63a5", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.cell_based.SetupNotebookTest() # Set up the test" + ] + }, + { + "cell_type": "markdown", + "id": "4a7aca97", + "metadata": {}, + "source": [ + "In the third test we run a node-based simulation restricted to the surface of a sphere.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4a308eb3", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.core.OutputFileHandler(\"Python/TestNodeBasedCellSimulationsRestrictedSpheroidTutorial\")\n", + "nodes = []\n", + "nodes.append(chaste.mesh.Node[3](0, False, 0.5, 0.0, 0.0))\n", + "nodes.append(chaste.mesh.Node[3](1, False, -0.5, 0.0, 0.0))\n", + "nodes.append(chaste.mesh.Node[3](2, False, 0.0, 0.5, 0.0))\n", + "nodes.append(chaste.mesh.Node[3](3, False, 0.0, -0.5, 0.0))\n", + "mesh = chaste.mesh.NodesOnlyMesh[3]()" + ] + }, + { + "cell_type": "markdown", + "id": "e8f52cd8", + "metadata": {}, + "source": [ + "To run node-based simulations you need to define a cut off length (second argument in ConstructNodesWithoutMesh),\n", + "which defines the connectivity of the nodes by defining a radius of interaction.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "71cf51c1", + "metadata": {}, + "outputs": [], + "source": [ + "mesh.ConstructNodesWithoutMesh(nodes, 1.5)\n", + "transit_type = chaste.cell_based.TransitCellProliferativeType()\n", + "cell_generator = chaste.cell_based.CellsGenerator[\"UniformCellCycleModel\", 3]()\n", + "cells = cell_generator.GenerateBasicRandom(mesh.GetNumNodes(), transit_type)\n", + "cell_population = chaste.cell_based.NodeBasedCellPopulation[3](mesh, cells)" + ] + }, + { + "cell_type": "markdown", + "id": "360f80ee", + "metadata": {}, + "source": [ + "We can set up a `VtkScene` to do a quick visualization of the population before running the analysis.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "229588ce", + "metadata": {}, + "outputs": [], + "source": [ + "scene = chaste.visualization.VtkScene[3]()\n", + "scene.SetCellPopulation(cell_population)\n", + "nb_manager.vtk_show(scene, height=600)\n", + "simulator = chaste.cell_based.OffLatticeSimulation[3, 3](cell_population)\n", + "simulator.SetOutputDirectory(\"Python/TestNodeBasedCellSimulationsRestrictedSpheroidTutorial\")\n", + "simulator.SetSamplingTimestepMultiple(12)\n", + "simulator.SetEndTime(10.0)" + ] + }, + { + "cell_type": "markdown", + "id": "772404f8", + "metadata": {}, + "source": [ + "We now pass a force law to the simulation.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8f328139", + "metadata": {}, + "outputs": [], + "source": [ + "force = chaste.cell_based.GeneralisedLinearSpringForce[3, 3]()\n", + "simulator.AddForce(force)" + ] + }, + { + "cell_type": "markdown", + "id": "c0ba5622", + "metadata": {}, + "source": [ + "This time we create a CellPopulationBoundaryCondition and pass this to the OffLatticeSimulation.\n", + "Here we use a SphereGeometryBoundaryCondition which restricts cells to lie on a sphere (in 3D) or circle (in 2D).\n", + "For a list of possible boundary conditions see subclasses of AbstractCellPopulationBoundaryCondition.\n", + "Note that some of these boundary conditions are not compatible with node-based simulations see the specific class documentation\n", + "for details, if you try to use an incompatible class then you will receive a warning.\n", + "First we set the centre (0,0,1) and radius of the sphere (1).\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3c4ddafe", + "metadata": {}, + "outputs": [], + "source": [ + "centre = np.array([0.0, 0.0, 1.0])\n", + "radius = 5.0\n", + "point2 = chaste.mesh.ChastePoint[3](centre)\n", + "boundary_condition = chaste.cell_based.SphereGeometryBoundaryCondition[3](cell_population, point2.rGetLocation(), radius)\n", + "simulator.AddCellPopulationBoundaryCondition(boundary_condition)" + ] + }, + { + "cell_type": "markdown", + "id": "e5334799", + "metadata": {}, + "source": [ + "Save snapshot images of the population during the simulation\n", + "scene_modifier = chaste.cell_based.VtkSceneModifier[3]()\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a1965881", + "metadata": {}, + "outputs": [], + "source": [ + "scene_modifier.SetVtkScene(scene)\n", + "scene_modifier.SetUpdateFrequency(100)\n", + "simulator.AddSimulationModifier(scene_modifier)" + ] + }, + { + "cell_type": "markdown", + "id": "28307877", + "metadata": {}, + "source": [ + "To run the simulation, we call `Solve()`. We can again do a quick rendering of the population at the end of the simulation\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c0d53fb5", + "metadata": {}, + "outputs": [], + "source": [ + "scene.Start()\n", + "simulator.Solve()\n", + "scene.End()" + ] + }, + { + "cell_type": "markdown", + "id": "7e200047", + "metadata": {}, + "source": [ + "The next two lines are for test purposes only and are not part of this tutorial.\n", + "If different simulation input parameters are being explored the lines should be removed.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "05a71bf2", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.cell_based.TearDownNotebookTest() # Tear down the test" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pychaste/src/py/doc/tutorial/NodeBasedCellSimulations.md b/pychaste/src/py/doc/tutorial/NodeBasedCellSimulations.md new file mode 100644 index 0000000000..dfb63d93e4 --- /dev/null +++ b/pychaste/src/py/doc/tutorial/NodeBasedCellSimulations.md @@ -0,0 +1,471 @@ +--- +title : "Node Based Cell Simulations" +summary: "" +draft: false +images: [] +toc: true +layout: "single" +--- +This tutorial is automatically generated from [TestPyNodeBasedCellSimulationsTutorial.py](https://github.com/Chaste/Chaste/blob/develop/pychaste/test/tutorial/TestPyNodeBasedCellSimulationsTutorial.py) at revision [4045f91a83f5](https://github.com/Chaste/Chaste/commit/4045f91a83f55dc4a97f2ca4f97b0c32f4e43a4a). + +Note that the code is given in full at the bottom of the page. + +## Introduction +In this tutorial we show how Chaste can be used to create, run and visualize node-based simulations. Full details of the mechanical model can be found in Pathamathan et +al "A computational study of discrete mechanical tissue models", Physical Biology. Vol. 6. No. 3. 2009.. DOI (10.1088/1478-3975/6/3/036001). + +## The Test + +```python +import unittest # Python testing framework + +import numpy as np # Matrix tools + +import chaste # The PyChaste module +import chaste.cell_based # Contains cell populations +import chaste.mesh # Contains meshes +import chaste.visualization # Visualization tools + +from chaste.cell_based import AbstractCellBasedTestSuite + +class TestPyNodeBasedCellSimulationsTutorial(AbstractCellBasedTestSuite): +``` +### Test 1 - A basic node-based simulation +In the first test, we run a simple node-based simulation, in which we create a monolayer of cells, +using a nodes only mesh. Each cell is assigned a uniform cell-cycle model. + +```python + def test_monolayer(self): + + # JUPYTER_SETUP +``` +The first thing we do is generate a nodes only mesh. To do this we first create a `MutableMesh` to use as a generating mesh. +To do this we can use the `HoneycombMeshGenerator`. This generates a honeycomb-shaped mesh, in which all nodes are equidistant. +Here the first and second arguments define the size of the mesh - we have chosen a mesh that is 2 nodes (i.e. cells) wide, and 2 nodes high. + +```python + chaste.core.OutputFileHandler("Python/TestNodeBasedCellSimulationsTutorial") + generator = chaste.mesh.HoneycombMeshGenerator(2, 2) + generating_mesh = generator.GetMesh() +``` +Once we have a MutableMesh we can generate a NodesOnlyMesh from it using the following commands. +Note you can also generate the NodesOnlyMesh from a collection of nodes. + +```python + mesh = chaste.mesh.NodesOnlyMesh[2]() +``` +To run node-based simulations you need to define a cut off length (second argument in `ConstructNodesWithoutMesh`), +which defines the connectivity of the nodes by defining a radius of interaction. + +```python + mesh.ConstructNodesWithoutMesh(generating_mesh, 1.5) +``` +Having created a mesh, we now create a (wrapped) vector of CellPtrs. To do this, we use the `CellsGenerator` helper class, +which is specialized for the type of cell model required (here `UniformCellCycleModel`) and the dimension. +We create an empty vector of cells and pass this into the method along with the mesh. +The second argument represents the size of that the vector cells should become - one cell for each node, +the third argument specifies the proliferative type of the cell. + +```python + transit_type = chaste.cell_based.TransitCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumNodes(), transit_type) +``` +Now we have a mesh and a set of cells to go with it, we can create a `CellPopulation`. +In general, this class associates a collection of cells with a mesh. For this test, +because we have a `NodesOnlyMesh`, we use a particular type of cell population called a `NodeBasedCellPopulation`. + +```python + cell_population = chaste.cell_based.NodeBasedCellPopulation[2](mesh, cells) +``` +We can set up a `VtkScene` to do a quick visualization of the population before running the analysis. + +```python + scene = chaste.visualization.VtkScene[2]() + scene.SetCellPopulation(cell_population) + # JUPYTER_SHOW_FIRST + scene.Start() # JUPYTER_SHOW +``` +We then pass in the cell population into an `OffLatticeSimulation`, and set the output directory, output multiple and end time + +```python + simulator = chaste.cell_based.OffLatticeSimulation[2, 2](cell_population) + simulator.SetOutputDirectory("Python/TestNodeBasedCellSimulationsTutorial") + simulator.SetSamplingTimestepMultiple(100) + simulator.SetEndTime(10.0) +``` +We now pass a force law to the simulation. + +```python + force = chaste.cell_based.GeneralisedLinearSpringForce[2, 2]() + simulator.AddForce(force) +``` +Save snapshot images of the population during the simulation + +```python + scene_modifier = chaste.cell_based.VtkSceneModifier[2]() + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(100) + simulator.AddSimulationModifier(scene_modifier) +``` +To run the simulation, we call `Solve()`. We can again do a quick rendering of the population at the end of the simulation + +```python + scene.Start() + simulator.Solve() + scene.End() +``` +The next two lines are for test purposes only and are not part of this tutorial. +If different simulation input parameters are being explored the lines should be removed. + +```python + self.assertEqual(cell_population.GetNumRealCells(), 8) + self.assertAlmostEqual(chaste.cell_based.SimulationTime.Instance().GetTime(), 10.0, 6) + + # JUPYTER_TEARDOWN +``` +### Test 2 - a basic node-based simulation in 3D +In the second test we run a simple node-based simulation in 3D. This is very similar to the 2D test with the dimension changed from 2 to 3 and +instead of using a mesh generator we generate the nodes directly. + +```python + def test_spheroid(self): + + # JUPYTER_SETUP +``` +First, we generate a nodes only mesh. This time we specify the nodes manually by first creating a vector of nodes + +```python + chaste.core.OutputFileHandler( + "Python/TestNodeBasedCellSimulationsSpheroidTutorial" + ) + nodes = [] + nodes.append(chaste.mesh.Node[3](0, False, 0.5, 0.0, 0.0)) + nodes.append(chaste.mesh.Node[3](1, False, -0.5, 0.0, 0.0)) + nodes.append(chaste.mesh.Node[3](2, False, 0.0, 0.5, 0.0)) + nodes.append(chaste.mesh.Node[3](3, False, 0.0, -0.5, 0.0)) +``` +Finally a NodesOnlyMesh is created and the vector of nodes is passed to the ConstructNodesWithoutMesh method. + +```python + mesh = chaste.mesh.NodesOnlyMesh[3]() +``` +To run node-based simulations you need to define a cut off length (second argument in ConstructNodesWithoutMesh), +which defines the connectivity of the nodes by defining a radius of interaction. + +```python + mesh.ConstructNodesWithoutMesh(nodes, 1.5) +``` +Having created a mesh, we now create a std::vector of CellPtrs. +As before, we do this with the CellsGenerator helper class (this time with dimension 3). + +```python + transit_type = chaste.cell_based.TransitCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformCellCycleModel", 3]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumNodes(), transit_type) +``` +Now we have a mesh and a set of cells to go with it, we can create a `CellPopulation`. +In general, this class associates a collection of cells with a mesh. For this test, +because we have a `NodesOnlyMesh`, we use a particular type of cell population called a `NodeBasedCellPopulation`. + +```python + cell_population = chaste.cell_based.NodeBasedCellPopulation[3](mesh, cells) +``` +We can set up a `VtkScene` to do a quick visualization of the population before running the analysis. + +```python + scene = chaste.visualization.VtkScene[3]() + scene.SetCellPopulation(cell_population) + scene.Start() # JUPYTER_SHOW +``` +We then pass in the cell population into an `OffLatticeSimulation`, and set the output directory, output multiple and end time + +```python + simulator = chaste.cell_based.OffLatticeSimulation[3, 3](cell_population) + simulator.SetOutputDirectory("Python/TestNodeBasedCellSimulationsSpheroidTutorial") + simulator.SetSamplingTimestepMultiple(12) + simulator.SetEndTime(10.0) +``` +We now pass a force law to the simulation. + +```python + force = chaste.cell_based.GeneralisedLinearSpringForce[3, 3]() + simulator.AddForce(force) +``` +Save snapshot images of the population during the simulation + +```python + scene_modifier = chaste.cell_based.VtkSceneModifier[3]() + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(100) + simulator.AddSimulationModifier(scene_modifier) +``` +To run the simulation, we call `Solve()`. We can again do a quick rendering of the population at the end of the simulation + +```python + scene.Start() + simulator.Solve() + scene.End() +``` +The next two lines are for test purposes only and are not part of this tutorial. +If different simulation input parameters are being explored the lines should be removed. + +```python + self.assertEqual(cell_population.GetNumRealCells(), 8) + self.assertAlmostEqual(chaste.cell_based.SimulationTime.Instance().GetTime(), 10.0, 6) + + # JUPYTER_TEARDOWN +``` +### Test 3 - a node-based simulation on a restricted geometry +In the second test we run a simple node-based simulation in 3D. This is very similar to the 2D test with the dimension changed from 2 to 3 and +instead of using a mesh generator we generate the nodes directly. + +```python + def test_spheroid_on_sphere(self): + + # JUPYTER_SETUP +``` +In the third test we run a node-based simulation restricted to the surface of a sphere. + +```python + chaste.core.OutputFileHandler("Python/TestNodeBasedCellSimulationsRestrictedSpheroidTutorial") + nodes = [] + nodes.append(chaste.mesh.Node[3](0, False, 0.5, 0.0, 0.0)) + nodes.append(chaste.mesh.Node[3](1, False, -0.5, 0.0, 0.0)) + nodes.append(chaste.mesh.Node[3](2, False, 0.0, 0.5, 0.0)) + nodes.append(chaste.mesh.Node[3](3, False, 0.0, -0.5, 0.0)) + mesh = chaste.mesh.NodesOnlyMesh[3]() +``` +To run node-based simulations you need to define a cut off length (second argument in ConstructNodesWithoutMesh), +which defines the connectivity of the nodes by defining a radius of interaction. + +```python + mesh.ConstructNodesWithoutMesh(nodes, 1.5) + + transit_type = chaste.cell_based.TransitCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformCellCycleModel", 3]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumNodes(), transit_type) + cell_population = chaste.cell_based.NodeBasedCellPopulation[3](mesh, cells) +``` +We can set up a `VtkScene` to do a quick visualization of the population before running the analysis. + +```python + scene = chaste.visualization.VtkScene[3]() + scene.SetCellPopulation(cell_population) + scene.Start() # JUPYTER_SHOW + + simulator = chaste.cell_based.OffLatticeSimulation[3, 3](cell_population) + simulator.SetOutputDirectory("Python/TestNodeBasedCellSimulationsRestrictedSpheroidTutorial") + simulator.SetSamplingTimestepMultiple(12) + simulator.SetEndTime(10.0) +``` +We now pass a force law to the simulation. + +```python + force = chaste.cell_based.GeneralisedLinearSpringForce[3, 3]() + simulator.AddForce(force) +``` +This time we create a CellPopulationBoundaryCondition and pass this to the OffLatticeSimulation. +Here we use a SphereGeometryBoundaryCondition which restricts cells to lie on a sphere (in 3D) or circle (in 2D). +For a list of possible boundary conditions see subclasses of AbstractCellPopulationBoundaryCondition. +Note that some of these boundary conditions are not compatible with node-based simulations see the specific class documentation +for details, if you try to use an incompatible class then you will receive a warning. +First we set the centre (0,0,1) and radius of the sphere (1). + +```python + centre = np.array([0.0, 0.0, 1.0]) + radius = 5.0 + point2 = chaste.mesh.ChastePoint[3](centre) + boundary_condition = chaste.cell_based.SphereGeometryBoundaryCondition[3](cell_population, point2.rGetLocation(), radius) + simulator.AddCellPopulationBoundaryCondition(boundary_condition) +``` +Save snapshot images of the population during the simulation +scene_modifier = chaste.cell_based.VtkSceneModifier[3]() + +```python + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(100) + simulator.AddSimulationModifier(scene_modifier) +``` +To run the simulation, we call `Solve()`. We can again do a quick rendering of the population at the end of the simulation + +```python + scene.Start() + simulator.Solve() + scene.End() +``` +The next two lines are for test purposes only and are not part of this tutorial. +If different simulation input parameters are being explored the lines should be removed. + +```python + self.assertEqual(cell_population.GetNumRealCells(), 8) + self.assertAlmostEqual(chaste.cell_based.SimulationTime.Instance().GetTime(), 10.0, 6) + + # JUPYTER_TEARDOWN + +if __name__ == "__main__": + unittest.main(verbosity=2) +``` + +## Full code + +```python +import unittest # Python testing framework + +import numpy as np # Matrix tools + +import chaste # The PyChaste module +import chaste.cell_based # Contains cell populations +import chaste.mesh # Contains meshes +import chaste.visualization # Visualization tools + +from chaste.cell_based import AbstractCellBasedTestSuite + +class TestPyNodeBasedCellSimulationsTutorial(AbstractCellBasedTestSuite): + def test_monolayer(self): + + # JUPYTER_SETUP + + chaste.core.OutputFileHandler("Python/TestNodeBasedCellSimulationsTutorial") + generator = chaste.mesh.HoneycombMeshGenerator(2, 2) + generating_mesh = generator.GetMesh() + + mesh = chaste.mesh.NodesOnlyMesh[2]() + + mesh.ConstructNodesWithoutMesh(generating_mesh, 1.5) + + transit_type = chaste.cell_based.TransitCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumNodes(), transit_type) + + cell_population = chaste.cell_based.NodeBasedCellPopulation[2](mesh, cells) + + scene = chaste.visualization.VtkScene[2]() + scene.SetCellPopulation(cell_population) + # JUPYTER_SHOW_FIRST + scene.Start() # JUPYTER_SHOW + + simulator = chaste.cell_based.OffLatticeSimulation[2, 2](cell_population) + simulator.SetOutputDirectory("Python/TestNodeBasedCellSimulationsTutorial") + simulator.SetSamplingTimestepMultiple(100) + simulator.SetEndTime(10.0) + + force = chaste.cell_based.GeneralisedLinearSpringForce[2, 2]() + simulator.AddForce(force) + + scene_modifier = chaste.cell_based.VtkSceneModifier[2]() + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(100) + simulator.AddSimulationModifier(scene_modifier) + + scene.Start() + simulator.Solve() + scene.End() + + self.assertEqual(cell_population.GetNumRealCells(), 8) + self.assertAlmostEqual(chaste.cell_based.SimulationTime.Instance().GetTime(), 10.0, 6) + + # JUPYTER_TEARDOWN + + def test_spheroid(self): + + # JUPYTER_SETUP + + chaste.core.OutputFileHandler( + "Python/TestNodeBasedCellSimulationsSpheroidTutorial" + ) + nodes = [] + nodes.append(chaste.mesh.Node[3](0, False, 0.5, 0.0, 0.0)) + nodes.append(chaste.mesh.Node[3](1, False, -0.5, 0.0, 0.0)) + nodes.append(chaste.mesh.Node[3](2, False, 0.0, 0.5, 0.0)) + nodes.append(chaste.mesh.Node[3](3, False, 0.0, -0.5, 0.0)) + + mesh = chaste.mesh.NodesOnlyMesh[3]() + + mesh.ConstructNodesWithoutMesh(nodes, 1.5) + + transit_type = chaste.cell_based.TransitCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformCellCycleModel", 3]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumNodes(), transit_type) + + cell_population = chaste.cell_based.NodeBasedCellPopulation[3](mesh, cells) + + scene = chaste.visualization.VtkScene[3]() + scene.SetCellPopulation(cell_population) + scene.Start() # JUPYTER_SHOW + + simulator = chaste.cell_based.OffLatticeSimulation[3, 3](cell_population) + simulator.SetOutputDirectory("Python/TestNodeBasedCellSimulationsSpheroidTutorial") + simulator.SetSamplingTimestepMultiple(12) + simulator.SetEndTime(10.0) + + force = chaste.cell_based.GeneralisedLinearSpringForce[3, 3]() + simulator.AddForce(force) + + scene_modifier = chaste.cell_based.VtkSceneModifier[3]() + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(100) + simulator.AddSimulationModifier(scene_modifier) + + scene.Start() + simulator.Solve() + scene.End() + + self.assertEqual(cell_population.GetNumRealCells(), 8) + self.assertAlmostEqual(chaste.cell_based.SimulationTime.Instance().GetTime(), 10.0, 6) + + # JUPYTER_TEARDOWN + + def test_spheroid_on_sphere(self): + + # JUPYTER_SETUP + + chaste.core.OutputFileHandler("Python/TestNodeBasedCellSimulationsRestrictedSpheroidTutorial") + nodes = [] + nodes.append(chaste.mesh.Node[3](0, False, 0.5, 0.0, 0.0)) + nodes.append(chaste.mesh.Node[3](1, False, -0.5, 0.0, 0.0)) + nodes.append(chaste.mesh.Node[3](2, False, 0.0, 0.5, 0.0)) + nodes.append(chaste.mesh.Node[3](3, False, 0.0, -0.5, 0.0)) + mesh = chaste.mesh.NodesOnlyMesh[3]() + + mesh.ConstructNodesWithoutMesh(nodes, 1.5) + + transit_type = chaste.cell_based.TransitCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformCellCycleModel", 3]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumNodes(), transit_type) + cell_population = chaste.cell_based.NodeBasedCellPopulation[3](mesh, cells) + + scene = chaste.visualization.VtkScene[3]() + scene.SetCellPopulation(cell_population) + scene.Start() # JUPYTER_SHOW + + simulator = chaste.cell_based.OffLatticeSimulation[3, 3](cell_population) + simulator.SetOutputDirectory("Python/TestNodeBasedCellSimulationsRestrictedSpheroidTutorial") + simulator.SetSamplingTimestepMultiple(12) + simulator.SetEndTime(10.0) + + force = chaste.cell_based.GeneralisedLinearSpringForce[3, 3]() + simulator.AddForce(force) + + centre = np.array([0.0, 0.0, 1.0]) + radius = 5.0 + point2 = chaste.mesh.ChastePoint[3](centre) + boundary_condition = chaste.cell_based.SphereGeometryBoundaryCondition[3](cell_population, point2.rGetLocation(), radius) + simulator.AddCellPopulationBoundaryCondition(boundary_condition) + + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(100) + simulator.AddSimulationModifier(scene_modifier) + + scene.Start() + simulator.Solve() + scene.End() + + self.assertEqual(cell_population.GetNumRealCells(), 8) + self.assertAlmostEqual(chaste.cell_based.SimulationTime.Instance().GetTime(), 10.0, 6) + + # JUPYTER_TEARDOWN + +if __name__ == "__main__": + unittest.main(verbosity=2) +``` diff --git a/pychaste/src/py/doc/tutorial/PottsBasedCellSimulations.ipynb b/pychaste/src/py/doc/tutorial/PottsBasedCellSimulations.ipynb new file mode 100644 index 0000000000..1826a874d5 --- /dev/null +++ b/pychaste/src/py/doc/tutorial/PottsBasedCellSimulations.ipynb @@ -0,0 +1,873 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "7fde1a45", + "metadata": {}, + "source": [ + "This tutorial is automatically generated from [TestPyPottsBasedCellSimulationsTutorial.py](https://github.com/Chaste/Chaste/blob/develop/pychaste/test/tutorial/TestPyPottsBasedCellSimulationsTutorial.py) at revision [4045f91a83f5](https://github.com/Chaste/Chaste/commit/4045f91a83f55dc4a97f2ca4f97b0c32f4e43a4a)." + ] + }, + { + "cell_type": "markdown", + "id": "0ca32923", + "metadata": {}, + "source": [ + "\n", + "## Introduction\n", + "In this tutorial we show how Chaste can be used to create, run and visualize Potts-based simulations.\n", + "Full details of the mathematical model can be found in Graner, F. and Glazier, J. A. (1992).\n", + "\n", + "## The Test\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4a826be4", + "metadata": {}, + "outputs": [], + "source": [ + "import chaste # The PyChaste module\n", + "import chaste.cell_based # Contains cell populations\n", + "import chaste.mesh # Contains meshes\n", + "import chaste.visualization # Visualization tools\n", + "from chaste.cell_based import AbstractCellBasedTestSuite" + ] + }, + { + "cell_type": "markdown", + "id": "d0a923c5", + "metadata": {}, + "source": [ + "### Test 1 - A basic node-based simulation\n", + "In the first test, we run a simple Potts-based simulation, in which we create a monolayer of cells, using a Potts mesh.\n", + "Each cell is assigned a stochastic cell-cycle model.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e375c96f", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.cell_based.SetupNotebookTest() # Set up the test" + ] + }, + { + "cell_type": "markdown", + "id": "4891f34a", + "metadata": {}, + "source": [ + "First, we generate a Potts mesh. To create a PottsMesh, we can use the PottsMeshGenerator.\n", + "This generates a regular square-shaped mesh, in which all elements are the same size.\n", + "Here the first three arguments specify the domain width; the number of elements across; and the width of elements.\n", + "The second set of three arguments specify the domain height; the number of elements up; and the height of individual elements.\n", + "We have chosen a 2 by 2 block of elements, each consisting of 4 by 4 ( = 16) lattice sites.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ec809ee1", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.core.OutputFileHandler(\"Python/TestPottsBasedCellSimulationsTutorial\")\n", + "generator = chaste.mesh.PottsMeshGenerator[2](50, 2, 4, 50, 2, 4)\n", + "mesh = generator.GetMesh()" + ] + }, + { + "cell_type": "markdown", + "id": "1159f485", + "metadata": {}, + "source": [ + "Having created a mesh, we now create a vector of CellPtrs. To do this, we the CellsGenerator helper class,\n", + "which is templated over the type of cell model required and the dimension.\n", + "We create an empty vector of cells and pass this into the method along with the mesh.\n", + "The second argument represents the size of that the vector cells should become - one cell for each element.\n", + "Third argument makes all cells proliferate.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "93ae0159", + "metadata": {}, + "outputs": [], + "source": [ + "cell_generator = chaste.cell_based.CellsGenerator[\"UniformCellCycleModel\", 2]()\n", + "cells = cell_generator.GenerateBasic(mesh.GetNumElements())" + ] + }, + { + "cell_type": "markdown", + "id": "79019d11", + "metadata": {}, + "source": [ + "Now we have a mesh and a set of cells to go with it, we can create a CellPopulation.\n", + "In general, this class associates a collection of cells with a mesh. For this test, because we have a PottsMesh,\n", + "we use a particular type of cell population called a PottsBasedCellPopulation.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "237c6152", + "metadata": {}, + "outputs": [], + "source": [ + "cell_population = chaste.cell_based.PottsBasedCellPopulation[2](mesh, cells)" + ] + }, + { + "cell_type": "markdown", + "id": "6ca7a073", + "metadata": {}, + "source": [ + "We can set the \"Temperature\" to be used in the Potts Simulation using the optional command below. The default value is 0.1.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eccf2311", + "metadata": {}, + "outputs": [], + "source": [ + "cell_population.SetTemperature(0.1)" + ] + }, + { + "cell_type": "markdown", + "id": "5877351c", + "metadata": {}, + "source": [ + "By default the Potts simulation will make 1 sweep over the whole domain per timestep.\n", + "To use a different number of sweeps per timestep use the command.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e01e4f6a", + "metadata": {}, + "outputs": [], + "source": [ + "cell_population.SetNumSweepsPerTimestep(1)" + ] + }, + { + "cell_type": "markdown", + "id": "fff0c5aa", + "metadata": {}, + "source": [ + "We can set up a `VtkScene` to do a quick visualization of the population before running the analysis.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0590a5bc", + "metadata": {}, + "outputs": [], + "source": [ + "scene = chaste.visualization.VtkScene[2]()\n", + "scene.SetCellPopulation(cell_population)\n", + "scene.GetCellPopulationActorGenerator().SetShowPottsMeshEdges(True)\n", + "nb_manager = chaste.visualization.JupyterNotebookManager()\n", + "nb_manager.vtk_show(scene, height=600)" + ] + }, + { + "cell_type": "markdown", + "id": "b361412c", + "metadata": {}, + "source": [ + "We then pass in the cell population into an `OffLatticeSimulation`, and set the output directory and end time\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d2bfa4f8", + "metadata": {}, + "outputs": [], + "source": [ + "simulator = chaste.cell_based.OnLatticeSimulation[2](cell_population)\n", + "simulator.SetOutputDirectory(\"Python/TestPottsBasedCellSimulationsTutorial\")\n", + "simulator.SetEndTime(50.0)" + ] + }, + { + "cell_type": "markdown", + "id": "c67f4b5f", + "metadata": {}, + "source": [ + "The default timestep is 0.1, but can be changed using the below command. The timestep is used in conjunction with the \"Temperature\"\n", + "and number of sweeps per timestep to specify the relationship between cell movement and proliferation.\n", + "We also set the simulation to only output every 10 steps i.e. once per hour.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2c684b1f", + "metadata": {}, + "outputs": [], + "source": [ + "simulator.SetDt(0.1)\n", + "simulator.SetSamplingTimestepMultiple(10)" + ] + }, + { + "cell_type": "markdown", + "id": "0457c801", + "metadata": {}, + "source": [ + "We must now create one or more update rules, which determine the Hamiltonian in the Potts simulation.\n", + "For this test, we use two update rules based upon a volume constraint (VolumeConstraintPottsUpdateRule)\n", + "and adhesion between cells (AdhesionPottsUpdateRule) and pass them to the OnLatticeSimulation.\n", + "For a list of possible update rules see subclasses of AbstractPottsUpdateRule.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6b979a20", + "metadata": {}, + "outputs": [], + "source": [ + "volume_constraint_update_rule = (chaste.cell_based.VolumeConstraintPottsUpdateRule[2]())" + ] + }, + { + "cell_type": "markdown", + "id": "c27a4565", + "metadata": {}, + "source": [ + "Set an appropriate target volume in number of lattice sites. Here we use the default value of 16 lattice sites.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f284b928", + "metadata": {}, + "outputs": [], + "source": [ + "volume_constraint_update_rule.SetMatureCellTargetVolume(16)" + ] + }, + { + "cell_type": "markdown", + "id": "2f551b3d", + "metadata": {}, + "source": [ + "You can also vary the deformation energy parameter.\n", + "The larger the parameter the more cells will try to maintain target volume. Here we use the default value of 0.2.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e9283fe6", + "metadata": {}, + "outputs": [], + "source": [ + "volume_constraint_update_rule.SetDeformationEnergyParameter(0.2)" + ] + }, + { + "cell_type": "markdown", + "id": "378cd371", + "metadata": {}, + "source": [ + "Finally we add the update rule to the simulator.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "46fef4e2", + "metadata": {}, + "outputs": [], + "source": [ + "simulator.AddUpdateRule(volume_constraint_update_rule)" + ] + }, + { + "cell_type": "markdown", + "id": "b0909919", + "metadata": {}, + "source": [ + "We repeat the process for any other update rules.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "be387fe3", + "metadata": {}, + "outputs": [], + "source": [ + "adhesion_update_rule = chaste.cell_based.AdhesionPottsUpdateRule[2]()\n", + "simulator.AddUpdateRule(adhesion_update_rule)" + ] + }, + { + "cell_type": "markdown", + "id": "a9d998f5", + "metadata": {}, + "source": [ + "Save snapshot images of the population during the simulation\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "81267da0", + "metadata": {}, + "outputs": [], + "source": [ + "scene_modifier = chaste.cell_based.VtkSceneModifier[2]()\n", + "scene_modifier.SetVtkScene(scene)\n", + "scene_modifier.SetUpdateFrequency(100)\n", + "simulator.AddSimulationModifier(scene_modifier)" + ] + }, + { + "cell_type": "markdown", + "id": "84155781", + "metadata": {}, + "source": [ + "To run the simulation, we call `Solve()`. We can again do a quick rendering of the population at the end of the simulation\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "23157d8c", + "metadata": {}, + "outputs": [], + "source": [ + "scene.Start()\n", + "simulator.Solve()" + ] + }, + { + "cell_type": "markdown", + "id": "c0cab46c", + "metadata": {}, + "source": [ + "The next two lines are for test purposes only and are not part of this tutorial.\n", + "If different simulation input parameters are being explored the lines should be removed.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c8803141", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.cell_based.TearDownNotebookTest() # Tear down the test" + ] + }, + { + "cell_type": "markdown", + "id": "ecbe3664", + "metadata": {}, + "source": [ + "### Test 2 - Cell sorting\n", + "The next test generates a collection of cells, there are two types of cells, labelled ones and non labelled ones,\n", + "there is differential adhesion between the cell types. For the parameters specified, the cells sort into separate types.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "41e6737c", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.cell_based.SetupNotebookTest() # Set up the test" + ] + }, + { + "cell_type": "markdown", + "id": "7fba9e57", + "metadata": {}, + "source": [ + "First, we generate a Potts mesh. To create a PottsMesh, we can use the PottsMeshGenerator.\n", + "This generates a regular square-shaped mesh, in which all elements are the same size.\n", + "We have chosen an 8 by 8 block of elements each consisting of 4 by 4 ( = 16) lattice sites.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9d11ffcc", + "metadata": {}, + "outputs": [], + "source": [ + "generator = chaste.mesh.PottsMeshGenerator[2](50, 8, 4, 50, 8, 4)\n", + "mesh = generator.GetMesh()" + ] + }, + { + "cell_type": "markdown", + "id": "10e5a5b3", + "metadata": {}, + "source": [ + "Having created a mesh, we now create a VectorSharedPtrCells. To do this, we the CellsGenerator helper class,\n", + "as before but this time the third argument is set to make all cells non-proliferative.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6c226ef5", + "metadata": {}, + "outputs": [], + "source": [ + "differentiated_type = chaste.cell_based.DifferentiatedCellProliferativeType()\n", + "cell_generator = chaste.cell_based.CellsGenerator[\"UniformCellCycleModel\", 2]()\n", + "cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), differentiated_type)" + ] + }, + { + "cell_type": "markdown", + "id": "1ab34143", + "metadata": {}, + "source": [ + "Before we make a CellPopulation we make a cell label and then assign this label to some randomly chosen cells.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ae0a786f", + "metadata": {}, + "outputs": [], + "source": [ + "label = chaste.cell_based.CellLabel()\n", + "for eachCell in cells:\n", + " if chaste.core.RandomNumberGenerator.Instance().ranf() < 0.5:\n", + " eachCell.AddCellProperty(label)" + ] + }, + { + "cell_type": "markdown", + "id": "0bb348cd", + "metadata": {}, + "source": [ + "Now we have a mesh and a set of cells to go with it, we can create a CellPopulation.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "90709cfc", + "metadata": {}, + "outputs": [], + "source": [ + "cell_population = chaste.cell_based.PottsBasedCellPopulation[2](mesh, cells)" + ] + }, + { + "cell_type": "markdown", + "id": "8f6c4b45", + "metadata": {}, + "source": [ + "In order to visualize labelled cells we need to use the following command.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0934fd84", + "metadata": {}, + "outputs": [], + "source": [ + "cell_population.AddCellWriterCellLabelWriter()" + ] + }, + { + "cell_type": "markdown", + "id": "e053108a", + "metadata": {}, + "source": [ + "We then pass in the cell population into an `OffLatticeSimulation`, and set the output directory and end time\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10e3a562", + "metadata": {}, + "outputs": [], + "source": [ + "simulator = chaste.cell_based.OnLatticeSimulation[2](cell_population)\n", + "simulator.SetOutputDirectory(\"Python/TestPottsBasedCellSorting\")\n", + "simulator.SetEndTime(20.0)\n", + "simulator.SetSamplingTimestepMultiple(10)" + ] + }, + { + "cell_type": "markdown", + "id": "5374f0fb", + "metadata": {}, + "source": [ + "We must now create one or more update rules, which determine the Hamiltonian in the Potts simulation.\n", + "For this test, we use two update rules based upon a volume constraint (VolumeConstraintPottsUpdateRule) and\n", + "differential adhesion between cells (DifferentialAdhesionPottsUpdateRule), set appropriate parameters, and\n", + "pass them to the OnLatticeSimulation.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "36b33de7", + "metadata": {}, + "outputs": [], + "source": [ + "volume_constraint_update_rule = chaste.cell_based.VolumeConstraintPottsUpdateRule[2]()\n", + "volume_constraint_update_rule.SetMatureCellTargetVolume(16)\n", + "volume_constraint_update_rule.SetDeformationEnergyParameter(0.2)\n", + "simulator.AddUpdateRule(volume_constraint_update_rule)" + ] + }, + { + "cell_type": "markdown", + "id": "b703ab38", + "metadata": {}, + "source": [ + "We repeat the process for any other update rules.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "baf36cc8", + "metadata": {}, + "outputs": [], + "source": [ + "differential_adhesion_update_rule = chaste.cell_based.DifferentialAdhesionPottsUpdateRule[2]()\n", + "differential_adhesion_update_rule.SetLabelledCellLabelledCellAdhesionEnergyParameter(0.16)\n", + "differential_adhesion_update_rule.SetLabelledCellCellAdhesionEnergyParameter(0.11)\n", + "differential_adhesion_update_rule.SetCellCellAdhesionEnergyParameter(0.02)\n", + "differential_adhesion_update_rule.SetLabelledCellBoundaryAdhesionEnergyParameter(0.16)\n", + "differential_adhesion_update_rule.SetCellBoundaryAdhesionEnergyParameter(0.16)\n", + "simulator.AddUpdateRule(differential_adhesion_update_rule)" + ] + }, + { + "cell_type": "markdown", + "id": "4af74ae4", + "metadata": {}, + "source": [ + "To run the simulation, we call `Solve()`.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9c35c5f2", + "metadata": {}, + "outputs": [], + "source": [ + "simulator.Solve()" + ] + }, + { + "cell_type": "markdown", + "id": "739d1c69", + "metadata": {}, + "source": [ + "The next two lines are for test purposes only and are not part of this tutorial.\n", + "If different simulation input parameters are being explored the lines should be removed.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "150cc3c5", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.cell_based.TearDownNotebookTest() # Tear down the test" + ] + }, + { + "cell_type": "markdown", + "id": "b6424474", + "metadata": {}, + "source": [ + "### Test 3 - 3D Cell Sorting\n", + "The next test extends the previous example to three dimensions.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "959081d3", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.cell_based.SetupNotebookTest() # Set up the test" + ] + }, + { + "cell_type": "markdown", + "id": "b636fcbe", + "metadata": {}, + "source": [ + "First, we generate a Potts mesh. To create a PottsMesh, we can use the PottsMeshGenerator.\n", + "This generates a regular square-shaped mesh, in which all elements are the same size.\n", + "Here the first three arguments specify the domain width; the number of elements across; and the width of elements.\n", + "The second set of three arguments specify the domain height; the number of elements up; and the height of individual elements.\n", + "The third set of three arguments specify the domain depth; the number of elements deep; and the depth of individual elements.\n", + "We have chosen an 4 by 4 by 4 ( = 64) block of elements each consisting of 2 by 2 by 2 ( = 8) lattice sites.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bd4451d1", + "metadata": {}, + "outputs": [], + "source": [ + "generator = chaste.mesh.PottsMeshGenerator[3](10, 4, 2, 10, 4, 2, 10, 4, 2)\n", + "mesh = generator.GetMesh()" + ] + }, + { + "cell_type": "markdown", + "id": "0f3452b3", + "metadata": {}, + "source": [ + "Having created a mesh, we now create a VectorSharedPtrCells. To do this, we the CellsGenerator helper class,\n", + "as before but this time the third argument is set to make all cells non-proliferative.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3b4afec2", + "metadata": {}, + "outputs": [], + "source": [ + "differentiated_type = chaste.cell_based.DifferentiatedCellProliferativeType()\n", + "cell_generator = chaste.cell_based.CellsGenerator[\"UniformCellCycleModel\", 3]()\n", + "cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), differentiated_type)" + ] + }, + { + "cell_type": "markdown", + "id": "219fa9d3", + "metadata": {}, + "source": [ + "As for the 2D case before we make a CellPopulation we make a pointer to a cell label and then assign this label to some randomly chosen cells.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "51fa8f3c", + "metadata": {}, + "outputs": [], + "source": [ + "label = chaste.cell_based.CellLabel()\n", + "for eachCell in cells:\n", + " if chaste.core.RandomNumberGenerator.Instance().ranf() < 0.5:\n", + " eachCell.AddCellProperty(label)" + ] + }, + { + "cell_type": "markdown", + "id": "29153736", + "metadata": {}, + "source": [ + "Now we have a mesh and a set of cells to go with it, we can create a CellPopulation.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6dccc493", + "metadata": {}, + "outputs": [], + "source": [ + "cell_population = chaste.cell_based.PottsBasedCellPopulation[3](mesh, cells)" + ] + }, + { + "cell_type": "markdown", + "id": "20ef2421", + "metadata": {}, + "source": [ + "In order to visualize labelled cells we need to use the following command.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a69b89e2", + "metadata": {}, + "outputs": [], + "source": [ + "cell_population.AddCellWriterCellLabelWriter()" + ] + }, + { + "cell_type": "markdown", + "id": "c71a8089", + "metadata": {}, + "source": [ + "We then pass in the cell population into an `OffLatticeSimulation`, and set the output directory and end time\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f163c556", + "metadata": {}, + "outputs": [], + "source": [ + "simulator = chaste.cell_based.OnLatticeSimulation[3](cell_population)\n", + "simulator.SetOutputDirectory(\"Python/TestPottsBasedCellSorting3D\")\n", + "simulator.SetEndTime(20.0)\n", + "simulator.SetSamplingTimestepMultiple(10)" + ] + }, + { + "cell_type": "markdown", + "id": "1cd5a008", + "metadata": {}, + "source": [ + "We must now create one or more update rules, which determine the Hamiltonian in the Potts simulation.\n", + "Now set the target volume to be appropriate for this 3D simulation.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "702c7c91", + "metadata": {}, + "outputs": [], + "source": [ + "volume_constraint_update_rule = chaste.cell_based.VolumeConstraintPottsUpdateRule[3]()\n", + "volume_constraint_update_rule.SetMatureCellTargetVolume(8)\n", + "volume_constraint_update_rule.SetDeformationEnergyParameter(0.2)\n", + "simulator.AddUpdateRule(volume_constraint_update_rule)" + ] + }, + { + "cell_type": "markdown", + "id": "26c1a0c4", + "metadata": {}, + "source": [ + "We use the same differential adhesion parameters as in the 2D case.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9fc4afb5", + "metadata": {}, + "outputs": [], + "source": [ + "differential_adhesion_update_rule = chaste.cell_based.DifferentialAdhesionPottsUpdateRule[3]()\n", + "differential_adhesion_update_rule.SetLabelledCellLabelledCellAdhesionEnergyParameter(0.16)\n", + "differential_adhesion_update_rule.SetLabelledCellCellAdhesionEnergyParameter(0.11)\n", + "differential_adhesion_update_rule.SetCellCellAdhesionEnergyParameter(0.02)\n", + "differential_adhesion_update_rule.SetLabelledCellBoundaryAdhesionEnergyParameter(0.16)\n", + "differential_adhesion_update_rule.SetCellBoundaryAdhesionEnergyParameter(0.16)\n", + "simulator.AddUpdateRule(differential_adhesion_update_rule)" + ] + }, + { + "cell_type": "markdown", + "id": "b55138ae", + "metadata": {}, + "source": [ + "To run the simulation, we call `Solve()`.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e8bc54d3", + "metadata": {}, + "outputs": [], + "source": [ + "simulator.Solve()" + ] + }, + { + "cell_type": "markdown", + "id": "1b460e01", + "metadata": {}, + "source": [ + "The next two lines are for test purposes only and are not part of this tutorial.\n", + "If different simulation input parameters are being explored the lines should be removed.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6761057d", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.cell_based.TearDownNotebookTest() # Tear down the test" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pychaste/src/py/doc/tutorial/PottsBasedCellSimulations.md b/pychaste/src/py/doc/tutorial/PottsBasedCellSimulations.md new file mode 100644 index 0000000000..dedd8070b8 --- /dev/null +++ b/pychaste/src/py/doc/tutorial/PottsBasedCellSimulations.md @@ -0,0 +1,493 @@ +--- +title : "Potts Based Cell Simulations" +summary: "" +draft: false +images: [] +toc: true +layout: "single" +--- +This tutorial is automatically generated from [TestPyPottsBasedCellSimulationsTutorial.py](https://github.com/Chaste/Chaste/blob/develop/pychaste/test/tutorial/TestPyPottsBasedCellSimulationsTutorial.py) at revision [4045f91a83f5](https://github.com/Chaste/Chaste/commit/4045f91a83f55dc4a97f2ca4f97b0c32f4e43a4a). + +Note that the code is given in full at the bottom of the page. + +## Introduction +In this tutorial we show how Chaste can be used to create, run and visualize Potts-based simulations. +Full details of the mathematical model can be found in Graner, F. and Glazier, J. A. (1992). + +## The Test + +```python +import unittest # Python testing framework + +import chaste # The PyChaste module +import chaste.cell_based # Contains cell populations +import chaste.mesh # Contains meshes +import chaste.visualization # Visualization tools + +from chaste.cell_based import AbstractCellBasedTestSuite + +class TestPyPottsBasedCellSimulationsTutorial(AbstractCellBasedTestSuite): +``` +### Test 1 - A basic node-based simulation +In the first test, we run a simple Potts-based simulation, in which we create a monolayer of cells, using a Potts mesh. +Each cell is assigned a stochastic cell-cycle model. + +```python + def test_monolayer(self): + + # JUPYTER_SETUP +``` +First, we generate a Potts mesh. To create a PottsMesh, we can use the PottsMeshGenerator. +This generates a regular square-shaped mesh, in which all elements are the same size. +Here the first three arguments specify the domain width; the number of elements across; and the width of elements. +The second set of three arguments specify the domain height; the number of elements up; and the height of individual elements. +We have chosen a 2 by 2 block of elements, each consisting of 4 by 4 ( = 16) lattice sites. + +```python + chaste.core.OutputFileHandler("Python/TestPottsBasedCellSimulationsTutorial") + generator = chaste.mesh.PottsMeshGenerator[2](50, 2, 4, 50, 2, 4) + mesh = generator.GetMesh() +``` +Having created a mesh, we now create a vector of CellPtrs. To do this, we the CellsGenerator helper class, +which is templated over the type of cell model required and the dimension. +We create an empty vector of cells and pass this into the method along with the mesh. +The second argument represents the size of that the vector cells should become - one cell for each element. +Third argument makes all cells proliferate. + +```python + cell_generator = chaste.cell_based.CellsGenerator["UniformCellCycleModel", 2]() + cells = cell_generator.GenerateBasic(mesh.GetNumElements()) +``` +Now we have a mesh and a set of cells to go with it, we can create a CellPopulation. +In general, this class associates a collection of cells with a mesh. For this test, because we have a PottsMesh, +we use a particular type of cell population called a PottsBasedCellPopulation. + +```python + cell_population = chaste.cell_based.PottsBasedCellPopulation[2](mesh, cells) +``` +We can set the "Temperature" to be used in the Potts Simulation using the optional command below. The default value is 0.1. + +```python + cell_population.SetTemperature(0.1) +``` +By default the Potts simulation will make 1 sweep over the whole domain per timestep. +To use a different number of sweeps per timestep use the command. + +```python + cell_population.SetNumSweepsPerTimestep(1) +``` +We can set up a `VtkScene` to do a quick visualization of the population before running the analysis. + +```python + scene = chaste.visualization.VtkScene[2]() + scene.SetCellPopulation(cell_population) + scene.GetCellPopulationActorGenerator().SetShowPottsMeshEdges(True) + # JUPYTER_SHOW_FIRST + scene.Start() # JUPYTER_SHOW +``` +We then pass in the cell population into an `OffLatticeSimulation`, and set the output directory and end time + +```python + simulator = chaste.cell_based.OnLatticeSimulation[2](cell_population) + simulator.SetOutputDirectory("Python/TestPottsBasedCellSimulationsTutorial") + simulator.SetEndTime(50.0) +``` +The default timestep is 0.1, but can be changed using the below command. The timestep is used in conjunction with the "Temperature" +and number of sweeps per timestep to specify the relationship between cell movement and proliferation. +We also set the simulation to only output every 10 steps i.e. once per hour. + +```python + simulator.SetDt(0.1) + simulator.SetSamplingTimestepMultiple(10) +``` +We must now create one or more update rules, which determine the Hamiltonian in the Potts simulation. +For this test, we use two update rules based upon a volume constraint (VolumeConstraintPottsUpdateRule) +and adhesion between cells (AdhesionPottsUpdateRule) and pass them to the OnLatticeSimulation. +For a list of possible update rules see subclasses of AbstractPottsUpdateRule. + +```python + volume_constraint_update_rule = (chaste.cell_based.VolumeConstraintPottsUpdateRule[2]()) +``` +Set an appropriate target volume in number of lattice sites. Here we use the default value of 16 lattice sites. + +```python + volume_constraint_update_rule.SetMatureCellTargetVolume(16) +``` +You can also vary the deformation energy parameter. +The larger the parameter the more cells will try to maintain target volume. Here we use the default value of 0.2. + +```python + volume_constraint_update_rule.SetDeformationEnergyParameter(0.2) +``` +Finally we add the update rule to the simulator. + +```python + simulator.AddUpdateRule(volume_constraint_update_rule) +``` +We repeat the process for any other update rules. + +```python + adhesion_update_rule = chaste.cell_based.AdhesionPottsUpdateRule[2]() + simulator.AddUpdateRule(adhesion_update_rule) +``` +Save snapshot images of the population during the simulation + +```python + scene_modifier = chaste.cell_based.VtkSceneModifier[2]() + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(100) + simulator.AddSimulationModifier(scene_modifier) +``` +To run the simulation, we call `Solve()`. We can again do a quick rendering of the population at the end of the simulation + +```python + scene.Start() + simulator.Solve() +``` +The next two lines are for test purposes only and are not part of this tutorial. +If different simulation input parameters are being explored the lines should be removed. + +```python + self.assertEqual(cell_population.GetNumRealCells(), 41) + self.assertAlmostEqual(chaste.cell_based.SimulationTime.Instance().GetTime(), 50.0, 6) + + # JUPYTER_TEARDOWN +``` +### Test 2 - Cell sorting +The next test generates a collection of cells, there are two types of cells, labelled ones and non labelled ones, +there is differential adhesion between the cell types. For the parameters specified, the cells sort into separate types. + +```python + def test_potts_monolayer_cell_sorting(self): + + # JUPYTER_SETUP +``` +First, we generate a Potts mesh. To create a PottsMesh, we can use the PottsMeshGenerator. +This generates a regular square-shaped mesh, in which all elements are the same size. +We have chosen an 8 by 8 block of elements each consisting of 4 by 4 ( = 16) lattice sites. + +```python + generator = chaste.mesh.PottsMeshGenerator[2](50, 8, 4, 50, 8, 4) + mesh = generator.GetMesh() +``` +Having created a mesh, we now create a VectorSharedPtrCells. To do this, we the CellsGenerator helper class, +as before but this time the third argument is set to make all cells non-proliferative. + +```python + differentiated_type = chaste.cell_based.DifferentiatedCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), differentiated_type) +``` +Before we make a CellPopulation we make a cell label and then assign this label to some randomly chosen cells. + +```python + label = chaste.cell_based.CellLabel() + for eachCell in cells: + if chaste.core.RandomNumberGenerator.Instance().ranf() < 0.5: + eachCell.AddCellProperty(label) +``` +Now we have a mesh and a set of cells to go with it, we can create a CellPopulation. + +```python + cell_population = chaste.cell_based.PottsBasedCellPopulation[2](mesh, cells) +``` +In order to visualize labelled cells we need to use the following command. + +```python + cell_population.AddCellWriterCellLabelWriter() +``` +We then pass in the cell population into an `OffLatticeSimulation`, and set the output directory and end time + +```python + simulator = chaste.cell_based.OnLatticeSimulation[2](cell_population) + simulator.SetOutputDirectory("Python/TestPottsBasedCellSorting") + simulator.SetEndTime(20.0) + simulator.SetSamplingTimestepMultiple(10) +``` +We must now create one or more update rules, which determine the Hamiltonian in the Potts simulation. +For this test, we use two update rules based upon a volume constraint (VolumeConstraintPottsUpdateRule) and +differential adhesion between cells (DifferentialAdhesionPottsUpdateRule), set appropriate parameters, and +pass them to the OnLatticeSimulation. + +```python + volume_constraint_update_rule = chaste.cell_based.VolumeConstraintPottsUpdateRule[2]() + volume_constraint_update_rule.SetMatureCellTargetVolume(16) + volume_constraint_update_rule.SetDeformationEnergyParameter(0.2) + simulator.AddUpdateRule(volume_constraint_update_rule) +``` +We repeat the process for any other update rules. + +```python + differential_adhesion_update_rule = chaste.cell_based.DifferentialAdhesionPottsUpdateRule[2]() + differential_adhesion_update_rule.SetLabelledCellLabelledCellAdhesionEnergyParameter(0.16) + differential_adhesion_update_rule.SetLabelledCellCellAdhesionEnergyParameter(0.11) + differential_adhesion_update_rule.SetCellCellAdhesionEnergyParameter(0.02) + differential_adhesion_update_rule.SetLabelledCellBoundaryAdhesionEnergyParameter(0.16) + differential_adhesion_update_rule.SetCellBoundaryAdhesionEnergyParameter(0.16) + simulator.AddUpdateRule(differential_adhesion_update_rule) +``` +To run the simulation, we call `Solve()`. + +```python + simulator.Solve() +``` +The next two lines are for test purposes only and are not part of this tutorial. +If different simulation input parameters are being explored the lines should be removed. + +```python + self.assertEqual(cell_population.GetNumRealCells(), 64) + self.assertAlmostEqual(chaste.cell_based.SimulationTime.Instance().GetTime(), 20.0, 6) + + # JUPYTER_TEARDOWN +``` +### Test 3 - 3D Cell Sorting +The next test extends the previous example to three dimensions. + +```python + def test_potts_spheroid_cell_sorting(self): + + # JUPYTER_SETUP +``` +First, we generate a Potts mesh. To create a PottsMesh, we can use the PottsMeshGenerator. +This generates a regular square-shaped mesh, in which all elements are the same size. +Here the first three arguments specify the domain width; the number of elements across; and the width of elements. +The second set of three arguments specify the domain height; the number of elements up; and the height of individual elements. +The third set of three arguments specify the domain depth; the number of elements deep; and the depth of individual elements. +We have chosen an 4 by 4 by 4 ( = 64) block of elements each consisting of 2 by 2 by 2 ( = 8) lattice sites. + +```python + generator = chaste.mesh.PottsMeshGenerator[3](10, 4, 2, 10, 4, 2, 10, 4, 2) + mesh = generator.GetMesh() +``` +Having created a mesh, we now create a VectorSharedPtrCells. To do this, we the CellsGenerator helper class, +as before but this time the third argument is set to make all cells non-proliferative. + +```python + differentiated_type = chaste.cell_based.DifferentiatedCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformCellCycleModel", 3]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), differentiated_type) +``` +As for the 2D case before we make a CellPopulation we make a pointer to a cell label and then assign this label to some randomly chosen cells. + +```python + label = chaste.cell_based.CellLabel() + for eachCell in cells: + if chaste.core.RandomNumberGenerator.Instance().ranf() < 0.5: + eachCell.AddCellProperty(label) +``` +Now we have a mesh and a set of cells to go with it, we can create a CellPopulation. + +```python + cell_population = chaste.cell_based.PottsBasedCellPopulation[3](mesh, cells) +``` +In order to visualize labelled cells we need to use the following command. + +```python + cell_population.AddCellWriterCellLabelWriter() +``` +We then pass in the cell population into an `OffLatticeSimulation`, and set the output directory and end time + +```python + simulator = chaste.cell_based.OnLatticeSimulation[3](cell_population) + simulator.SetOutputDirectory("Python/TestPottsBasedCellSorting3D") + simulator.SetEndTime(20.0) + simulator.SetSamplingTimestepMultiple(10) +``` +We must now create one or more update rules, which determine the Hamiltonian in the Potts simulation. +Now set the target volume to be appropriate for this 3D simulation. + +```python + volume_constraint_update_rule = chaste.cell_based.VolumeConstraintPottsUpdateRule[3]() + volume_constraint_update_rule.SetMatureCellTargetVolume(8) + volume_constraint_update_rule.SetDeformationEnergyParameter(0.2) + simulator.AddUpdateRule(volume_constraint_update_rule) +``` +We use the same differential adhesion parameters as in the 2D case. + +```python + differential_adhesion_update_rule = chaste.cell_based.DifferentialAdhesionPottsUpdateRule[3]() + differential_adhesion_update_rule.SetLabelledCellLabelledCellAdhesionEnergyParameter(0.16) + differential_adhesion_update_rule.SetLabelledCellCellAdhesionEnergyParameter(0.11) + differential_adhesion_update_rule.SetCellCellAdhesionEnergyParameter(0.02) + differential_adhesion_update_rule.SetLabelledCellBoundaryAdhesionEnergyParameter(0.16) + differential_adhesion_update_rule.SetCellBoundaryAdhesionEnergyParameter(0.16) + simulator.AddUpdateRule(differential_adhesion_update_rule) +``` +To run the simulation, we call `Solve()`. + +```python + simulator.Solve() +``` +The next two lines are for test purposes only and are not part of this tutorial. +If different simulation input parameters are being explored the lines should be removed. + +```python + self.assertEqual(cell_population.GetNumRealCells(), 64) + self.assertAlmostEqual(chaste.cell_based.SimulationTime.Instance().GetTime(), 20.0, 6) + + # JUPYTER_TEARDOWN + +if __name__ == "__main__": + unittest.main(verbosity=2) +``` + +## Full code + +```python +import unittest # Python testing framework + +import chaste # The PyChaste module +import chaste.cell_based # Contains cell populations +import chaste.mesh # Contains meshes +import chaste.visualization # Visualization tools + +from chaste.cell_based import AbstractCellBasedTestSuite + +class TestPyPottsBasedCellSimulationsTutorial(AbstractCellBasedTestSuite): + def test_monolayer(self): + + # JUPYTER_SETUP + + chaste.core.OutputFileHandler("Python/TestPottsBasedCellSimulationsTutorial") + generator = chaste.mesh.PottsMeshGenerator[2](50, 2, 4, 50, 2, 4) + mesh = generator.GetMesh() + + cell_generator = chaste.cell_based.CellsGenerator["UniformCellCycleModel", 2]() + cells = cell_generator.GenerateBasic(mesh.GetNumElements()) + + cell_population = chaste.cell_based.PottsBasedCellPopulation[2](mesh, cells) + + cell_population.SetTemperature(0.1) + + cell_population.SetNumSweepsPerTimestep(1) + + scene = chaste.visualization.VtkScene[2]() + scene.SetCellPopulation(cell_population) + scene.GetCellPopulationActorGenerator().SetShowPottsMeshEdges(True) + # JUPYTER_SHOW_FIRST + scene.Start() # JUPYTER_SHOW + + simulator = chaste.cell_based.OnLatticeSimulation[2](cell_population) + simulator.SetOutputDirectory("Python/TestPottsBasedCellSimulationsTutorial") + simulator.SetEndTime(50.0) + + simulator.SetDt(0.1) + simulator.SetSamplingTimestepMultiple(10) + + volume_constraint_update_rule = (chaste.cell_based.VolumeConstraintPottsUpdateRule[2]()) + + volume_constraint_update_rule.SetMatureCellTargetVolume(16) + + volume_constraint_update_rule.SetDeformationEnergyParameter(0.2) + + simulator.AddUpdateRule(volume_constraint_update_rule) + + adhesion_update_rule = chaste.cell_based.AdhesionPottsUpdateRule[2]() + simulator.AddUpdateRule(adhesion_update_rule) + + scene_modifier = chaste.cell_based.VtkSceneModifier[2]() + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(100) + simulator.AddSimulationModifier(scene_modifier) + + scene.Start() + simulator.Solve() + + self.assertEqual(cell_population.GetNumRealCells(), 41) + self.assertAlmostEqual(chaste.cell_based.SimulationTime.Instance().GetTime(), 50.0, 6) + + # JUPYTER_TEARDOWN + + def test_potts_monolayer_cell_sorting(self): + + # JUPYTER_SETUP + + generator = chaste.mesh.PottsMeshGenerator[2](50, 8, 4, 50, 8, 4) + mesh = generator.GetMesh() + + differentiated_type = chaste.cell_based.DifferentiatedCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), differentiated_type) + + label = chaste.cell_based.CellLabel() + for eachCell in cells: + if chaste.core.RandomNumberGenerator.Instance().ranf() < 0.5: + eachCell.AddCellProperty(label) + + cell_population = chaste.cell_based.PottsBasedCellPopulation[2](mesh, cells) + + cell_population.AddCellWriterCellLabelWriter() + + simulator = chaste.cell_based.OnLatticeSimulation[2](cell_population) + simulator.SetOutputDirectory("Python/TestPottsBasedCellSorting") + simulator.SetEndTime(20.0) + simulator.SetSamplingTimestepMultiple(10) + + volume_constraint_update_rule = chaste.cell_based.VolumeConstraintPottsUpdateRule[2]() + volume_constraint_update_rule.SetMatureCellTargetVolume(16) + volume_constraint_update_rule.SetDeformationEnergyParameter(0.2) + simulator.AddUpdateRule(volume_constraint_update_rule) + + differential_adhesion_update_rule = chaste.cell_based.DifferentialAdhesionPottsUpdateRule[2]() + differential_adhesion_update_rule.SetLabelledCellLabelledCellAdhesionEnergyParameter(0.16) + differential_adhesion_update_rule.SetLabelledCellCellAdhesionEnergyParameter(0.11) + differential_adhesion_update_rule.SetCellCellAdhesionEnergyParameter(0.02) + differential_adhesion_update_rule.SetLabelledCellBoundaryAdhesionEnergyParameter(0.16) + differential_adhesion_update_rule.SetCellBoundaryAdhesionEnergyParameter(0.16) + simulator.AddUpdateRule(differential_adhesion_update_rule) + + simulator.Solve() + + self.assertEqual(cell_population.GetNumRealCells(), 64) + self.assertAlmostEqual(chaste.cell_based.SimulationTime.Instance().GetTime(), 20.0, 6) + + # JUPYTER_TEARDOWN + + def test_potts_spheroid_cell_sorting(self): + + # JUPYTER_SETUP + + generator = chaste.mesh.PottsMeshGenerator[3](10, 4, 2, 10, 4, 2, 10, 4, 2) + mesh = generator.GetMesh() + + differentiated_type = chaste.cell_based.DifferentiatedCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformCellCycleModel", 3]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), differentiated_type) + + label = chaste.cell_based.CellLabel() + for eachCell in cells: + if chaste.core.RandomNumberGenerator.Instance().ranf() < 0.5: + eachCell.AddCellProperty(label) + + cell_population = chaste.cell_based.PottsBasedCellPopulation[3](mesh, cells) + + cell_population.AddCellWriterCellLabelWriter() + + simulator = chaste.cell_based.OnLatticeSimulation[3](cell_population) + simulator.SetOutputDirectory("Python/TestPottsBasedCellSorting3D") + simulator.SetEndTime(20.0) + simulator.SetSamplingTimestepMultiple(10) + + volume_constraint_update_rule = chaste.cell_based.VolumeConstraintPottsUpdateRule[3]() + volume_constraint_update_rule.SetMatureCellTargetVolume(8) + volume_constraint_update_rule.SetDeformationEnergyParameter(0.2) + simulator.AddUpdateRule(volume_constraint_update_rule) + + differential_adhesion_update_rule = chaste.cell_based.DifferentialAdhesionPottsUpdateRule[3]() + differential_adhesion_update_rule.SetLabelledCellLabelledCellAdhesionEnergyParameter(0.16) + differential_adhesion_update_rule.SetLabelledCellCellAdhesionEnergyParameter(0.11) + differential_adhesion_update_rule.SetCellCellAdhesionEnergyParameter(0.02) + differential_adhesion_update_rule.SetLabelledCellBoundaryAdhesionEnergyParameter(0.16) + differential_adhesion_update_rule.SetCellBoundaryAdhesionEnergyParameter(0.16) + simulator.AddUpdateRule(differential_adhesion_update_rule) + + simulator.Solve() + + self.assertEqual(cell_population.GetNumRealCells(), 64) + self.assertAlmostEqual(chaste.cell_based.SimulationTime.Instance().GetTime(), 20.0, 6) + + # JUPYTER_TEARDOWN + +if __name__ == "__main__": + unittest.main(verbosity=2) +``` diff --git a/pychaste/src/py/doc/tutorial/ScratchAssay.ipynb b/pychaste/src/py/doc/tutorial/ScratchAssay.ipynb new file mode 100644 index 0000000000..9ef06878f6 --- /dev/null +++ b/pychaste/src/py/doc/tutorial/ScratchAssay.ipynb @@ -0,0 +1,384 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "797b3161", + "metadata": {}, + "source": [ + "This tutorial is automatically generated from [TestPyScratchAssayTutorial.py](https://github.com/Chaste/Chaste/blob/develop/pychaste/test/tutorial/TestPyScratchAssayTutorial.py) at revision [4045f91a83f5](https://github.com/Chaste/Chaste/commit/4045f91a83f55dc4a97f2ca4f97b0c32f4e43a4a)." + ] + }, + { + "cell_type": "markdown", + "id": "13e8af81", + "metadata": {}, + "source": [ + "\n", + "## Introduction\n", + "This tutorial is an example of modelling a scratch assay using a simple cellular automaton\n", + "representation of cells. It will cover the following techniques:\n", + "\n", + " * Setting up a regular mesh (or lattice)\n", + " * Visualizing the mesh\n", + " * Working with file-based output\n", + " * Generating cells and adding them to the mesh\n", + " * Simulating cell migration on the mesh\n", + " * Real-time visualization of the cell population and plotting of population statistics\n", + " \n", + "## The Test\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0971bae7", + "metadata": {}, + "outputs": [], + "source": [ + "import chaste # The PyChaste module\n", + "import chaste.cell_based # Contains cell populations\n", + "import chaste.mesh # Contains meshes\n", + "import chaste.visualization # Visualization tools\n", + "import matplotlib.pyplot as plt # Plotting\n", + "import numpy as np # Matrix tools" + ] + }, + { + "cell_type": "markdown", + "id": "25b7fc3d", + "metadata": {}, + "source": [ + "### Test 1 - Scratch Assay\n", + "In this test we will create a scratch along the middle of a domain and quantify the migration\n", + "of cells into the region. Cells will migrate by random walk on the their regular mesh (lattice).\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9c4cc057", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.cell_based.SetupNotebookTest() # Set up the test" + ] + }, + { + "cell_type": "markdown", + "id": "ff6ccb89", + "metadata": {}, + "source": [ + "Chaste is based on the concept of `Cells` and `Meshes`. 'Cells' do not store their position in space,\n", + "or connectivity, these are managed by a `Mesh`. The first step in most Chaste simulations is to\n", + "set up a mesh, on which we can locate cells. A collection of `Cells` and a `Mesh` are a `CellPopulation`\n", + "in Chaste terminology. The most simple `CellPopulation` is the `CaBasedCellPopulation` which corresponds\n", + "to cells occupying discrete locations on a regular mesh (lattice). Our first step is to set up the mesh.\n", + "Here we set up a 2D lattice.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cabf5ac5", + "metadata": {}, + "outputs": [], + "source": [ + "num_points_in_x = 100\n", + "num_points_in_y = 12\n", + "generator = chaste.mesh.PottsMeshGenerator[2](num_points_in_x, 0, 0, num_points_in_y, 0, 0)\n", + "mesh = generator.GetMesh()" + ] + }, + { + "cell_type": "markdown", + "id": "7bdb5592", + "metadata": {}, + "source": [ + "Note that we are using a `PottsMeshGenerator[2]` to set up the grid and we are setting some terms to 0. Chaste\n", + "design is based on re-use of components, the `PottsMeshGenerator` can be used to set up other types of\n", + "cell population which require these extra terms. Note also the '[2]' at the end of the class name. This\n", + "tells us that we are working in 2D. Most Chaste classes are specialized (templated) for spatial dimension,\n", + "so we need to make sure we are consistent in the dimensionality of the classes we are using.\n", + "\n", + "Next we set up some cells. We create and empty container `VectorSharedPtrCell` (which will behave like a Python list)\n", + "and will fill it with cells of our chosen type. In Chaste cells can be assinged a number of proliferative types\n", + "(Default, Differentiated, Stem, Transit or User Defined). These types will define how cells behave in certain\n", + "simulations, for example whether they will proliferate. We just want our cells to migrate in this example, so\n", + "we set a DifferentiatedCellProliferativeType.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1a5c27ec", + "metadata": {}, + "outputs": [], + "source": [ + "cells = []\n", + "differentiated_type = chaste.cell_based.DifferentiatedCellProliferativeType()" + ] + }, + { + "cell_type": "markdown", + "id": "7b10a003", + "metadata": {}, + "source": [ + "We are not interested in cell cycling so we specialize the generator to NoCellCycleModel.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "96c1a7bb", + "metadata": {}, + "outputs": [], + "source": [ + "cell_generator = chaste.cell_based.CellsGenerator[\"NoCellCycleModel\", 2]()" + ] + }, + { + "cell_type": "markdown", + "id": "6e07ee85", + "metadata": {}, + "source": [ + "We want two sets of cells, starting on opposite sides of the mesh. We use `location_indices` to map cells onto\n", + "locations (or Nodes) on the mesh. For our regular mesh the Node indices increase fastest in x, then y. We will\n", + "add four layers of cells to each side of the mesh.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b6d726dd", + "metadata": {}, + "outputs": [], + "source": [ + "num_cell_layers = 4\n", + "bottom_location_indices = list(range(num_cell_layers * num_points_in_x))\n", + "num_grid_points = num_points_in_x * num_points_in_y\n", + "top_location_indices = list(\n", + " range(\n", + " num_grid_points - 1,\n", + " num_grid_points - num_cell_layers * num_points_in_x - 1,\n", + " -1,\n", + " )\n", + ")\n", + "cells = cell_generator.GenerateGivenLocationIndices(\n", + " bottom_location_indices + top_location_indices,\n", + " differentiated_type\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "eefc8541", + "metadata": {}, + "source": [ + "Now we have a mesh and a set of cells to go with it, we can create a CellPopulation.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8c86c544", + "metadata": {}, + "outputs": [], + "source": [ + "cell_population = chaste.cell_based.CaBasedCellPopulation[2](\n", + " mesh,\n", + " cells,\n", + " bottom_location_indices + top_location_indices\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "e5242d0f", + "metadata": {}, + "source": [ + "Next, we set up an `OffLatticeSimulation` which will manage the solver. We need to add some custom rules to\n", + "this solver to specify how we want the cells to migrate.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ca0a0b30", + "metadata": {}, + "outputs": [], + "source": [ + "simulator = chaste.cell_based.OnLatticeSimulation[2](cell_population)\n", + "simulator.SetOutputDirectory(\"Python/TestScratchAssayTutorial\")\n", + "simulator.SetEndTime(10.0)\n", + "simulator.SetDt(0.1)\n", + "simulator.SetSamplingTimestepMultiple(1)" + ] + }, + { + "cell_type": "markdown", + "id": "085a3274", + "metadata": {}, + "source": [ + "We must now create a rule for cell migration. We will use an existing diffusion type rule.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3caf09ae", + "metadata": {}, + "outputs": [], + "source": [ + "diffusion_update_rule = chaste.cell_based.DiffusionCaUpdateRule[2]()\n", + "simulator.AddUpdateRule(diffusion_update_rule)" + ] + }, + { + "cell_type": "markdown", + "id": "f4bd73a7", + "metadata": {}, + "source": [ + "PyChaste can do simple 3D rendering with VTK. We set up a `VtkScene` so that we can see the population\n", + "evovle in real time.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6ee19f49", + "metadata": {}, + "outputs": [], + "source": [ + "scene = chaste.visualization.VtkScene[2]()\n", + "scene.SetCellPopulation(cell_population)\n", + "scene.GetCellPopulationActorGenerator().SetShowCellCentres(True)\n", + "nb_manager = chaste.visualization.JupyterNotebookManager()\n", + "nb_manager.vtk_show(scene, height=600)" + ] + }, + { + "cell_type": "markdown", + "id": "4ec9165e", + "metadata": {}, + "source": [ + "We add the scene to the simulation for real-time updating using a `VtkSceneModifier`. Such\n", + "modifiers are called by the simulator at regular periods during the main time loop and\n", + "have access to the cell population. We will use a similar idea in a moment to record cell\n", + "positions for real time plotting.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c3917d9e", + "metadata": {}, + "outputs": [], + "source": [ + "scene_modifier = chaste.cell_based.VtkSceneModifier[2]()\n", + "scene_modifier.SetVtkScene(scene)\n", + "scene_modifier.SetUpdateFrequency(10)\n", + "simulator.AddSimulationModifier(scene_modifier)" + ] + }, + { + "cell_type": "markdown", + "id": "89d5f35a", + "metadata": {}, + "source": [ + "Chaste and PyChaste use object oriented programming. This may require some background reading,\n", + "but allows for great flexibility in terms of modifying existing functionality. In\n", + "order to pull the data we want out of the simulation as it runs we will create our own\n", + "simulation modifier class and use it for real time plotting. This Python class over-rides\n", + "one of the built-in classes, giving us access to the quantities we want during the simulation.\n", + "Usually we would define such a class in a different module and import it, it is placed\n", + "here for the purposes of the tutorial.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8f9f760f", + "metadata": {}, + "outputs": [], + "source": [ + "class PlottingModifier(chaste.cell_based.PythonSimulationModifier[2]):\n", + " \"\"\"Class for real time plotting of cell numbers using Matplotlib\"\"\"\n", + " def __init__(self, num_points_in_x, num_points_in_y):\n", + " super(PlottingModifier, self).__init__()\n", + " # Set up a figure for plotting\n", + " plt.ioff()\n", + " self.fig = plt.figure()\n", + " self.fig.ax = self.fig.add_subplot(111)\n", + " self.fig.ax.set_xlabel(\"y - Position (Cell Lengths)\")\n", + " self.fig.ax.set_ylabel(\"Number Of Cells\")\n", + " self.plot_frequency = 10 # only plot every 10 steps\n", + " self.num_points_in_x = num_points_in_x\n", + " self.num_points_in_y = num_points_in_y\n", + " def UpdateAtEndOfTimeStep(self, cell_population):\n", + " \"\"\"Plot the number of cells at each lattice point and time-point\n", + " Use the SimulationTime singleton to determine when to plot.\n", + " \"\"\"\n", + " num_increments = chaste.cell_based.SimulationTime.Instance().GetTimeStepsElapsed()\n", + " if num_increments % self.plot_frequency == 0:\n", + " y_locations = np.linspace(0, num_points_in_y, num_points_in_y)\n", + " num_cells = []\n", + " for idx in range(num_points_in_y):\n", + " counter = 0\n", + " for jdx in range(num_points_in_x):\n", + " if cell_population.IsCellAttachedToLocationIndex(\n", + " jdx + idx * num_points_in_x\n", + " ):\n", + " counter += 1\n", + " num_cells.append(counter)\n", + " self.fig.ax.plot(y_locations, num_cells, color=\"black\")\n", + " self.fig.canvas.draw()\n", + " # display.display(self.fig)\n", + " # display.clear_output(wait=True)\n", + " def SetupSolve(self, cell_population, output_directory):\n", + " \"\"\"Ensure the cell population is in the correct state at the start of the simulation\"\"\"\n", + " cell_population.Update()\n", + "plotting_modifier = PlottingModifier(num_points_in_x, num_points_in_y)\n", + "simulator.AddSimulationModifier(plotting_modifier)" + ] + }, + { + "cell_type": "markdown", + "id": "38156e0e", + "metadata": {}, + "source": [ + "To run the simulation, we call `Solve()` and optionally set up interactive plotting. We will see the cells\n", + "migrate and the population distribution gradually become more uniform.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "03c22afe", + "metadata": {}, + "outputs": [], + "source": [ + "scene.Start()\n", + "plt.ion()\n", + "plt.show()\n", + "simulator.Solve()\n", + "chaste.cell_based.TearDownNotebookTest() # Tear down the test" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pychaste/src/py/doc/tutorial/ScratchAssay.md b/pychaste/src/py/doc/tutorial/ScratchAssay.md new file mode 100644 index 0000000000..23e0e65062 --- /dev/null +++ b/pychaste/src/py/doc/tutorial/ScratchAssay.md @@ -0,0 +1,345 @@ +--- +title : "Scratch Assay" +summary: "" +draft: false +images: [] +toc: true +layout: "single" +--- +This tutorial is automatically generated from [TestPyScratchAssayTutorial.py](https://github.com/Chaste/Chaste/blob/develop/pychaste/test/tutorial/TestPyScratchAssayTutorial.py) at revision [4045f91a83f5](https://github.com/Chaste/Chaste/commit/4045f91a83f55dc4a97f2ca4f97b0c32f4e43a4a). + +Note that the code is given in full at the bottom of the page. + +## Introduction +This tutorial is an example of modelling a scratch assay using a simple cellular automaton +representation of cells. It will cover the following techniques: + + * Setting up a regular mesh (or lattice) + * Visualizing the mesh + * Working with file-based output + * Generating cells and adding them to the mesh + * Simulating cell migration on the mesh + * Real-time visualization of the cell population and plotting of population statistics + +## The Test + +```python +import unittest # Python testing framework + +import chaste # The PyChaste module +import chaste.cell_based # Contains cell populations +import chaste.mesh # Contains meshes +import chaste.visualization # Visualization tools +import matplotlib.pyplot as plt # Plotting +import numpy as np # Matrix tools + +class TestPyScratchAssayTutorial(chaste.cell_based.AbstractCellBasedTestSuite): +``` +### Test 1 - Scratch Assay +In this test we will create a scratch along the middle of a domain and quantify the migration +of cells into the region. Cells will migrate by random walk on the their regular mesh (lattice). + +```python + def test_single_scratch(self): + + # JUPYTER_SETUP +``` +Chaste is based on the concept of `Cells` and `Meshes`. 'Cells' do not store their position in space, +or connectivity, these are managed by a `Mesh`. The first step in most Chaste simulations is to +set up a mesh, on which we can locate cells. A collection of `Cells` and a `Mesh` are a `CellPopulation` +in Chaste terminology. The most simple `CellPopulation` is the `CaBasedCellPopulation` which corresponds +to cells occupying discrete locations on a regular mesh (lattice). Our first step is to set up the mesh. +Here we set up a 2D lattice. + +```python + num_points_in_x = 100 + num_points_in_y = 12 + generator = chaste.mesh.PottsMeshGenerator[2](num_points_in_x, 0, 0, num_points_in_y, 0, 0) + mesh = generator.GetMesh() +``` +Note that we are using a `PottsMeshGenerator[2]` to set up the grid and we are setting some terms to 0. Chaste +design is based on re-use of components, the `PottsMeshGenerator` can be used to set up other types of +cell population which require these extra terms. Note also the '[2]' at the end of the class name. This +tells us that we are working in 2D. Most Chaste classes are specialized (templated) for spatial dimension, +so we need to make sure we are consistent in the dimensionality of the classes we are using. + +Next we set up some cells. We create and empty container `VectorSharedPtrCell` (which will behave like a Python list) +and will fill it with cells of our chosen type. In Chaste cells can be assinged a number of proliferative types +(Default, Differentiated, Stem, Transit or User Defined). These types will define how cells behave in certain +simulations, for example whether they will proliferate. We just want our cells to migrate in this example, so +we set a DifferentiatedCellProliferativeType. + +```python + cells = [] + differentiated_type = chaste.cell_based.DifferentiatedCellProliferativeType() +``` +We are not interested in cell cycling so we specialize the generator to NoCellCycleModel. + +```python + cell_generator = chaste.cell_based.CellsGenerator["NoCellCycleModel", 2]() +``` +We want two sets of cells, starting on opposite sides of the mesh. We use `location_indices` to map cells onto +locations (or Nodes) on the mesh. For our regular mesh the Node indices increase fastest in x, then y. We will +add four layers of cells to each side of the mesh. + +```python + num_cell_layers = 4 + bottom_location_indices = list(range(num_cell_layers * num_points_in_x)) + num_grid_points = num_points_in_x * num_points_in_y + top_location_indices = list( + range( + num_grid_points - 1, + num_grid_points - num_cell_layers * num_points_in_x - 1, + -1, + ) + ) + cells = cell_generator.GenerateGivenLocationIndices( + bottom_location_indices + top_location_indices, + differentiated_type + ) +``` +Now we have a mesh and a set of cells to go with it, we can create a CellPopulation. + +```python + cell_population = chaste.cell_based.CaBasedCellPopulation[2]( + mesh, + cells, + bottom_location_indices + top_location_indices + ) +``` +Next, we set up an `OffLatticeSimulation` which will manage the solver. We need to add some custom rules to +this solver to specify how we want the cells to migrate. + +```python + simulator = chaste.cell_based.OnLatticeSimulation[2](cell_population) + simulator.SetOutputDirectory("Python/TestScratchAssayTutorial") + simulator.SetEndTime(10.0) + simulator.SetDt(0.1) + simulator.SetSamplingTimestepMultiple(1) +``` +We must now create a rule for cell migration. We will use an existing diffusion type rule. + +```python + diffusion_update_rule = chaste.cell_based.DiffusionCaUpdateRule[2]() + simulator.AddUpdateRule(diffusion_update_rule) +``` +PyChaste can do simple 3D rendering with VTK. We set up a `VtkScene` so that we can see the population +evovle in real time. + +```python + scene = chaste.visualization.VtkScene[2]() + scene.SetCellPopulation(cell_population) + scene.GetCellPopulationActorGenerator().SetShowCellCentres(True) + # JUPYTER_SHOW_FIRST + scene.Start() # JUPYTER_SHOW +``` +We add the scene to the simulation for real-time updating using a `VtkSceneModifier`. Such +modifiers are called by the simulator at regular periods during the main time loop and +have access to the cell population. We will use a similar idea in a moment to record cell +positions for real time plotting. + +```python + scene_modifier = chaste.cell_based.VtkSceneModifier[2]() + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(10) + simulator.AddSimulationModifier(scene_modifier) +``` +Chaste and PyChaste use object oriented programming. This may require some background reading, +but allows for great flexibility in terms of modifying existing functionality. In +order to pull the data we want out of the simulation as it runs we will create our own +simulation modifier class and use it for real time plotting. This Python class over-rides +one of the built-in classes, giving us access to the quantities we want during the simulation. +Usually we would define such a class in a different module and import it, it is placed +here for the purposes of the tutorial. + +```python + class PlottingModifier(chaste.cell_based.PythonSimulationModifier[2]): + """Class for real time plotting of cell numbers using Matplotlib""" + + def __init__(self, num_points_in_x, num_points_in_y): + super(PlottingModifier, self).__init__() + + # Set up a figure for plotting + plt.ioff() + self.fig = plt.figure() + self.fig.ax = self.fig.add_subplot(111) + self.fig.ax.set_xlabel("y - Position (Cell Lengths)") + self.fig.ax.set_ylabel("Number Of Cells") + self.plot_frequency = 10 # only plot every 10 steps + self.num_points_in_x = num_points_in_x + self.num_points_in_y = num_points_in_y + + def UpdateAtEndOfTimeStep(self, cell_population): + """Plot the number of cells at each lattice point and time-point + + Use the SimulationTime singleton to determine when to plot. + """ + + num_increments = chaste.cell_based.SimulationTime.Instance().GetTimeStepsElapsed() + if num_increments % self.plot_frequency == 0: + y_locations = np.linspace(0, num_points_in_y, num_points_in_y) + num_cells = [] + for idx in range(num_points_in_y): + counter = 0 + for jdx in range(num_points_in_x): + if cell_population.IsCellAttachedToLocationIndex( + jdx + idx * num_points_in_x + ): + counter += 1 + num_cells.append(counter) + + self.fig.ax.plot(y_locations, num_cells, color="black") + self.fig.canvas.draw() + # display.display(self.fig) + # display.clear_output(wait=True) + + def SetupSolve(self, cell_population, output_directory): + """Ensure the cell population is in the correct state at the start of the simulation""" + + cell_population.Update() + + plotting_modifier = PlottingModifier(num_points_in_x, num_points_in_y) + simulator.AddSimulationModifier(plotting_modifier) +``` +To run the simulation, we call `Solve()` and optionally set up interactive plotting. We will see the cells +migrate and the population distribution gradually become more uniform. + +```python + scene.Start() + plt.ion() + plt.show() + simulator.Solve() + + # JUPYTER_TEARDOWN + +if __name__ == "__main__": + unittest.main(verbosity=2) +``` + +## Full code + +```python +import unittest # Python testing framework + +import chaste # The PyChaste module +import chaste.cell_based # Contains cell populations +import chaste.mesh # Contains meshes +import chaste.visualization # Visualization tools +import matplotlib.pyplot as plt # Plotting +import numpy as np # Matrix tools + +class TestPyScratchAssayTutorial(chaste.cell_based.AbstractCellBasedTestSuite): + + def test_single_scratch(self): + + # JUPYTER_SETUP + + num_points_in_x = 100 + num_points_in_y = 12 + generator = chaste.mesh.PottsMeshGenerator[2](num_points_in_x, 0, 0, num_points_in_y, 0, 0) + mesh = generator.GetMesh() + + cells = [] + differentiated_type = chaste.cell_based.DifferentiatedCellProliferativeType() + + cell_generator = chaste.cell_based.CellsGenerator["NoCellCycleModel", 2]() + + num_cell_layers = 4 + bottom_location_indices = list(range(num_cell_layers * num_points_in_x)) + num_grid_points = num_points_in_x * num_points_in_y + top_location_indices = list( + range( + num_grid_points - 1, + num_grid_points - num_cell_layers * num_points_in_x - 1, + -1, + ) + ) + cells = cell_generator.GenerateGivenLocationIndices( + bottom_location_indices + top_location_indices, + differentiated_type + ) + + cell_population = chaste.cell_based.CaBasedCellPopulation[2]( + mesh, + cells, + bottom_location_indices + top_location_indices + ) + + simulator = chaste.cell_based.OnLatticeSimulation[2](cell_population) + simulator.SetOutputDirectory("Python/TestScratchAssayTutorial") + simulator.SetEndTime(10.0) + simulator.SetDt(0.1) + simulator.SetSamplingTimestepMultiple(1) + + diffusion_update_rule = chaste.cell_based.DiffusionCaUpdateRule[2]() + simulator.AddUpdateRule(diffusion_update_rule) + + scene = chaste.visualization.VtkScene[2]() + scene.SetCellPopulation(cell_population) + scene.GetCellPopulationActorGenerator().SetShowCellCentres(True) + # JUPYTER_SHOW_FIRST + scene.Start() # JUPYTER_SHOW + + scene_modifier = chaste.cell_based.VtkSceneModifier[2]() + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(10) + simulator.AddSimulationModifier(scene_modifier) + + class PlottingModifier(chaste.cell_based.PythonSimulationModifier[2]): + """Class for real time plotting of cell numbers using Matplotlib""" + + def __init__(self, num_points_in_x, num_points_in_y): + super(PlottingModifier, self).__init__() + + # Set up a figure for plotting + plt.ioff() + self.fig = plt.figure() + self.fig.ax = self.fig.add_subplot(111) + self.fig.ax.set_xlabel("y - Position (Cell Lengths)") + self.fig.ax.set_ylabel("Number Of Cells") + self.plot_frequency = 10 # only plot every 10 steps + self.num_points_in_x = num_points_in_x + self.num_points_in_y = num_points_in_y + + def UpdateAtEndOfTimeStep(self, cell_population): + """Plot the number of cells at each lattice point and time-point + + Use the SimulationTime singleton to determine when to plot. + """ + + num_increments = chaste.cell_based.SimulationTime.Instance().GetTimeStepsElapsed() + if num_increments % self.plot_frequency == 0: + y_locations = np.linspace(0, num_points_in_y, num_points_in_y) + num_cells = [] + for idx in range(num_points_in_y): + counter = 0 + for jdx in range(num_points_in_x): + if cell_population.IsCellAttachedToLocationIndex( + jdx + idx * num_points_in_x + ): + counter += 1 + num_cells.append(counter) + + self.fig.ax.plot(y_locations, num_cells, color="black") + self.fig.canvas.draw() + # display.display(self.fig) + # display.clear_output(wait=True) + + def SetupSolve(self, cell_population, output_directory): + """Ensure the cell population is in the correct state at the start of the simulation""" + + cell_population.Update() + + plotting_modifier = PlottingModifier(num_points_in_x, num_points_in_y) + simulator.AddSimulationModifier(plotting_modifier) + + scene.Start() + plt.ion() + plt.show() + simulator.Solve() + + # JUPYTER_TEARDOWN + +if __name__ == "__main__": + unittest.main(verbosity=2) +``` diff --git a/pychaste/src/py/doc/tutorial/Spheroid.ipynb b/pychaste/src/py/doc/tutorial/Spheroid.ipynb new file mode 100644 index 0000000000..4661858951 --- /dev/null +++ b/pychaste/src/py/doc/tutorial/Spheroid.ipynb @@ -0,0 +1,388 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "c099af98", + "metadata": {}, + "source": [ + "This tutorial is automatically generated from [TestPySpheroidTutorial.py](https://github.com/Chaste/Chaste/blob/develop/pychaste/test/tutorial/TestPySpheroidTutorial.py) at revision [4045f91a83f5](https://github.com/Chaste/Chaste/commit/4045f91a83f55dc4a97f2ca4f97b0c32f4e43a4a)." + ] + }, + { + "cell_type": "markdown", + "id": "08b132d1", + "metadata": {}, + "source": [ + "\n", + "## Introduction\n", + "This tutorial is an example of modelling spheroid growth with a nutrient.\n", + "It covers:\n", + " * Setting up an off-lattice cell population\n", + " * Setting up a cell cycle model with oxygen dependence\n", + " * Setting up and solving an oxygen transport PDE\n", + " * Setting up a cell killer\n", + " \n", + "## The Test\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8692d375", + "metadata": {}, + "outputs": [], + "source": [ + "import chaste # The PyChaste module\n", + "import chaste.cell_based # Contains cell populations\n", + "import chaste.mesh # Contains meshes\n", + "import chaste.pde # PDEs\n", + "import chaste.visualization # Visualization tools\n", + "import matplotlib.pyplot as plt # Plotting\n", + "import numpy as np # Matrix tools" + ] + }, + { + "cell_type": "markdown", + "id": "a79f54d3", + "metadata": {}, + "source": [ + "### Test 1 - a 2D mesh-based spheroid\n", + "In this test we set up a spheroid with a plentiful supply of oxygen on the boundary and watch it grow\n", + "over time. Cells can gradually become apoptotic if the oxygen tension is too low.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "84ad1851", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.cell_based.SetupNotebookTest() # Set up the test" + ] + }, + { + "cell_type": "markdown", + "id": "567073f1", + "metadata": {}, + "source": [ + "This time we will use on off-lattice `MeshBased` cell population. Cell centres are joined with\n", + "springs with a Delauney Triangulation used to identify neighbours. Cell area is given by the dual\n", + "(Voronoi Tesselation). We start off with a small number of cells. We use a `MutableMesh` which\n", + "can change connectivity over time and a `HoneycombMeshGenerator` to set it up with a simple\n", + "honeycomb pattern. Here the first and second arguments define the size of the mesh -\n", + "we have chosen a mesh that is 5 nodes (i.e. cells) wide, and 5 nodes high. The extra '2' argument puts\n", + "two layers of non-cell elements around the mesh, which help to form a nicer voronoi tesselation\n", + "for area calculations.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "07f23c5d", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.core.OutputFileHandler(\"Python/TestSpheroidTutorial\")\n", + "generator = chaste.mesh.HoneycombMeshGenerator(5, 5)\n", + "mesh = generator.GetMesh()" + ] + }, + { + "cell_type": "markdown", + "id": "d1c67572", + "metadata": {}, + "source": [ + "We create some cells next, with a stem-like proliferative type. This means they will continually\n", + "proliferate if there is enough oxygen, similar to how a tumour spheroid may behave.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8ef67d41", + "metadata": {}, + "outputs": [], + "source": [ + "stem_type = chaste.cell_based.StemCellProliferativeType()\n", + "cell_generator = chaste.cell_based.CellsGenerator[\"SimpleOxygenBasedCellCycleModel\", 2]()\n", + "cells = cell_generator.GenerateBasicRandom(mesh.GetNumNodes(), stem_type)" + ] + }, + { + "cell_type": "markdown", + "id": "afbe0b45", + "metadata": {}, + "source": [ + "Define when cells become apoptotic\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f57067ef", + "metadata": {}, + "outputs": [], + "source": [ + "for eachCell in cells:\n", + " cell_cycle_model = eachCell.GetCellCycleModel()\n", + " eachCell.GetCellData().SetItem(\"oxygen\", 30.0)\n", + " cell_cycle_model.SetDimension(2)\n", + " cell_cycle_model.SetStemCellG1Duration(4.0)\n", + " cell_cycle_model.SetHypoxicConcentration(0.1)\n", + " cell_cycle_model.SetQuiescentConcentration(0.3)\n", + " cell_cycle_model.SetCriticalHypoxicDuration(8)\n", + " g1_duration = cell_cycle_model.GetStemCellG1Duration()\n", + " sg2m_duration = cell_cycle_model.GetSG2MDuration()\n", + " rnum = chaste.core.RandomNumberGenerator.Instance().ranf()\n", + " birth_time = -rnum * (g1_duration + sg2m_duration)\n", + " eachCell.SetBirthTime(birth_time)" + ] + }, + { + "cell_type": "markdown", + "id": "8997f297", + "metadata": {}, + "source": [ + "Now we have a mesh and a set of cells to go with it, we can create a `CellPopulation` as before.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0b63a593", + "metadata": {}, + "outputs": [], + "source": [ + "cell_population = chaste.cell_based.MeshBasedCellPopulation[2, 2](mesh, cells)" + ] + }, + { + "cell_type": "markdown", + "id": "5ecd9ad7", + "metadata": {}, + "source": [ + "To view the results of this and the next test in Paraview it is necessary to explicitly generate the required .vtu files.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6c6defe1", + "metadata": {}, + "outputs": [], + "source": [ + "cell_population.AddPopulationWriterVoronoiDataWriter()" + ] + }, + { + "cell_type": "markdown", + "id": "283caea4", + "metadata": {}, + "source": [ + "We then pass in the cell population into an `OffLatticeSimulation`, and set the output directory and end time.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d22332d3", + "metadata": {}, + "outputs": [], + "source": [ + "simulator = chaste.cell_based.OffLatticeSimulation[2, 2](cell_population)\n", + "simulator.SetOutputDirectory(\"Python/TestSpheroidTutorial\")\n", + "simulator.SetEndTime(5.0)" + ] + }, + { + "cell_type": "markdown", + "id": "d80b788e", + "metadata": {}, + "source": [ + "We ask for output every 12 increments\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e9d2b2a9", + "metadata": {}, + "outputs": [], + "source": [ + "simulator.SetSamplingTimestepMultiple(100)" + ] + }, + { + "cell_type": "markdown", + "id": "82c88d9b", + "metadata": {}, + "source": [ + "We define how the springs between cells behave using a force law.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c7f7ca41", + "metadata": {}, + "outputs": [], + "source": [ + "force = chaste.cell_based.GeneralisedLinearSpringForce[2, 2]()\n", + "simulator.AddForce(force)" + ] + }, + { + "cell_type": "markdown", + "id": "a8949df8", + "metadata": {}, + "source": [ + "We set up a PDE for oxygen diffusion and consumption by cells, setting the rate of consumption to 0.1\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9138a798", + "metadata": {}, + "outputs": [], + "source": [ + "pde = chaste.pde.CellwiseSourceEllipticPde[2](cell_population, -0.5)" + ] + }, + { + "cell_type": "markdown", + "id": "2089b21a", + "metadata": {}, + "source": [ + "We set a constant amount of oxygen on the edge of the spheroid\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e911483c", + "metadata": {}, + "outputs": [], + "source": [ + "bc = chaste.pde.ConstBoundaryCondition[2](1.0)\n", + "is_neumann_bc = False" + ] + }, + { + "cell_type": "markdown", + "id": "1d3ca62f", + "metadata": {}, + "source": [ + "Set up a pde modifier to solve the PDE at each simulation time step\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8bafd14b", + "metadata": {}, + "outputs": [], + "source": [ + "# pde_modifier = chaste.cell_based.EllipticGrowingDomainPdeModifier[2](pde, bc, is_neumann_bc)\n", + "# pde_modifier.SetDependentVariableName(\"oxygen\")\n", + "# simulator.AddSimulationModifier(pde_modifier)" + ] + }, + { + "cell_type": "markdown", + "id": "0ecfebe0", + "metadata": {}, + "source": [ + "As before, we set up a scene modifier for real-time visualization\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "37affc74", + "metadata": {}, + "outputs": [], + "source": [ + "scene = chaste.visualization.VtkScene[2]()\n", + "scene.SetCellPopulation(cell_population)\n", + "scene.GetCellPopulationActorGenerator().SetColorByCellData(True)\n", + "scene.GetCellPopulationActorGenerator().SetDataLabel(\"oxygen\")\n", + "scene.GetCellPopulationActorGenerator().SetShowCellCentres(True)\n", + "scene.GetCellPopulationActorGenerator().SetShowVoronoiMeshEdges(False)\n", + "nb_manager = chaste.visualization.JupyterNotebookManager()\n", + "scene_modifier = chaste.cell_based.VtkSceneModifier[2]()\n", + "scene_modifier.SetVtkScene(scene)\n", + "scene_modifier.SetUpdateFrequency(100)\n", + "simulator.AddSimulationModifier(scene_modifier)" + ] + }, + { + "cell_type": "markdown", + "id": "4c044403", + "metadata": {}, + "source": [ + "Eventually remove apoptotic cells\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6c8b88fc", + "metadata": {}, + "outputs": [], + "source": [ + "killer = chaste.cell_based.ApoptoticCellKiller[2](cell_population)\n", + "simulator.AddCellKiller(killer)" + ] + }, + { + "cell_type": "markdown", + "id": "cc4a8ec7", + "metadata": {}, + "source": [ + "To run the simulation, we call `Solve()`. We can again do a quick rendering of the population at the end of the simulation\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "98a5b4eb", + "metadata": {}, + "outputs": [], + "source": [ + "scene.Start()\n", + "simulator.Solve()\n", + "chaste.cell_based.TearDownNotebookTest() # Tear down the test" + ] + }, + { + "cell_type": "markdown", + "id": "e7c1b978", + "metadata": {}, + "source": [ + "Full results can be visualized in Paraview from the `file_handler.GetOutputDirectoryFullPath()` directory.\n", + "\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pychaste/src/py/doc/tutorial/Spheroid.md b/pychaste/src/py/doc/tutorial/Spheroid.md new file mode 100644 index 0000000000..a521804553 --- /dev/null +++ b/pychaste/src/py/doc/tutorial/Spheroid.md @@ -0,0 +1,253 @@ +--- +title : "Spheroid" +summary: "" +draft: false +images: [] +toc: true +layout: "single" +--- +This tutorial is automatically generated from [TestPySpheroidTutorial.py](https://github.com/Chaste/Chaste/blob/develop/pychaste/test/tutorial/TestPySpheroidTutorial.py) at revision [4045f91a83f5](https://github.com/Chaste/Chaste/commit/4045f91a83f55dc4a97f2ca4f97b0c32f4e43a4a). + +Note that the code is given in full at the bottom of the page. + +## Introduction +This tutorial is an example of modelling spheroid growth with a nutrient. +It covers: + * Setting up an off-lattice cell population + * Setting up a cell cycle model with oxygen dependence + * Setting up and solving an oxygen transport PDE + * Setting up a cell killer + +## The Test + +```python +import unittest # Python testing framework + +import chaste # The PyChaste module +import chaste.cell_based # Contains cell populations +import chaste.mesh # Contains meshes +import chaste.pde # PDEs +import chaste.visualization # Visualization tools +import matplotlib.pyplot as plt # Plotting +import numpy as np # Matrix tools + +class TestPySpheroidTutorial(chaste.cell_based.AbstractCellBasedTestSuite): +``` +### Test 1 - a 2D mesh-based spheroid +In this test we set up a spheroid with a plentiful supply of oxygen on the boundary and watch it grow +over time. Cells can gradually become apoptotic if the oxygen tension is too low. + +```python + def test_spheroid(self): + + # JUPYTER_SETUP +``` +This time we will use on off-lattice `MeshBased` cell population. Cell centres are joined with +springs with a Delauney Triangulation used to identify neighbours. Cell area is given by the dual +(Voronoi Tesselation). We start off with a small number of cells. We use a `MutableMesh` which +can change connectivity over time and a `HoneycombMeshGenerator` to set it up with a simple +honeycomb pattern. Here the first and second arguments define the size of the mesh - +we have chosen a mesh that is 5 nodes (i.e. cells) wide, and 5 nodes high. The extra '2' argument puts +two layers of non-cell elements around the mesh, which help to form a nicer voronoi tesselation +for area calculations. + +```python + chaste.core.OutputFileHandler("Python/TestSpheroidTutorial") + generator = chaste.mesh.HoneycombMeshGenerator(5, 5) + mesh = generator.GetMesh() +``` +We create some cells next, with a stem-like proliferative type. This means they will continually +proliferate if there is enough oxygen, similar to how a tumour spheroid may behave. + +```python + stem_type = chaste.cell_based.StemCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["SimpleOxygenBasedCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumNodes(), stem_type) +``` +Define when cells become apoptotic + +```python + for eachCell in cells: + cell_cycle_model = eachCell.GetCellCycleModel() + eachCell.GetCellData().SetItem("oxygen", 30.0) + cell_cycle_model.SetDimension(2) + cell_cycle_model.SetStemCellG1Duration(4.0) + cell_cycle_model.SetHypoxicConcentration(0.1) + cell_cycle_model.SetQuiescentConcentration(0.3) + cell_cycle_model.SetCriticalHypoxicDuration(8) + g1_duration = cell_cycle_model.GetStemCellG1Duration() + sg2m_duration = cell_cycle_model.GetSG2MDuration() + rnum = chaste.core.RandomNumberGenerator.Instance().ranf() + birth_time = -rnum * (g1_duration + sg2m_duration) + eachCell.SetBirthTime(birth_time) +``` +Now we have a mesh and a set of cells to go with it, we can create a `CellPopulation` as before. + +```python + cell_population = chaste.cell_based.MeshBasedCellPopulation[2, 2](mesh, cells) +``` +To view the results of this and the next test in Paraview it is necessary to explicitly generate the required .vtu files. + +```python + cell_population.AddPopulationWriterVoronoiDataWriter() +``` +We then pass in the cell population into an `OffLatticeSimulation`, and set the output directory and end time. + +```python + simulator = chaste.cell_based.OffLatticeSimulation[2, 2](cell_population) + simulator.SetOutputDirectory("Python/TestSpheroidTutorial") + simulator.SetEndTime(5.0) +``` +We ask for output every 12 increments + +```python + simulator.SetSamplingTimestepMultiple(100) +``` +We define how the springs between cells behave using a force law. + +```python + force = chaste.cell_based.GeneralisedLinearSpringForce[2, 2]() + simulator.AddForce(force) +``` +We set up a PDE for oxygen diffusion and consumption by cells, setting the rate of consumption to 0.1 + +```python + pde = chaste.pde.CellwiseSourceEllipticPde[2](cell_population, -0.5) +``` +We set a constant amount of oxygen on the edge of the spheroid + +```python + bc = chaste.pde.ConstBoundaryCondition[2](1.0) + is_neumann_bc = False +``` +Set up a pde modifier to solve the PDE at each simulation time step + +```python + # pde_modifier = chaste.cell_based.EllipticGrowingDomainPdeModifier[2](pde, bc, is_neumann_bc) + # pde_modifier.SetDependentVariableName("oxygen") + # simulator.AddSimulationModifier(pde_modifier) +``` +As before, we set up a scene modifier for real-time visualization + +```python + scene = chaste.visualization.VtkScene[2]() + scene.SetCellPopulation(cell_population) + scene.GetCellPopulationActorGenerator().SetColorByCellData(True) + scene.GetCellPopulationActorGenerator().SetDataLabel("oxygen") + scene.GetCellPopulationActorGenerator().SetShowCellCentres(True) + scene.GetCellPopulationActorGenerator().SetShowVoronoiMeshEdges(False) + # JUPYTER_SHOW_FIRST + + scene_modifier = chaste.cell_based.VtkSceneModifier[2]() + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(100) + simulator.AddSimulationModifier(scene_modifier) +``` +Eventually remove apoptotic cells + +```python + killer = chaste.cell_based.ApoptoticCellKiller[2](cell_population) + simulator.AddCellKiller(killer) +``` +To run the simulation, we call `Solve()`. We can again do a quick rendering of the population at the end of the simulation + +```python + scene.Start() + simulator.Solve() + + # JUPYTER_TEARDOWN +``` +Full results can be visualized in Paraview from the `file_handler.GetOutputDirectoryFullPath()` directory. + +```python +if __name__ == "__main__": + unittest.main(verbosity=2) +``` + +## Full code + +```python +import unittest # Python testing framework + +import chaste # The PyChaste module +import chaste.cell_based # Contains cell populations +import chaste.mesh # Contains meshes +import chaste.pde # PDEs +import chaste.visualization # Visualization tools +import matplotlib.pyplot as plt # Plotting +import numpy as np # Matrix tools + +class TestPySpheroidTutorial(chaste.cell_based.AbstractCellBasedTestSuite): + + def test_spheroid(self): + + # JUPYTER_SETUP + + chaste.core.OutputFileHandler("Python/TestSpheroidTutorial") + generator = chaste.mesh.HoneycombMeshGenerator(5, 5) + mesh = generator.GetMesh() + + stem_type = chaste.cell_based.StemCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["SimpleOxygenBasedCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumNodes(), stem_type) + + for eachCell in cells: + cell_cycle_model = eachCell.GetCellCycleModel() + eachCell.GetCellData().SetItem("oxygen", 30.0) + cell_cycle_model.SetDimension(2) + cell_cycle_model.SetStemCellG1Duration(4.0) + cell_cycle_model.SetHypoxicConcentration(0.1) + cell_cycle_model.SetQuiescentConcentration(0.3) + cell_cycle_model.SetCriticalHypoxicDuration(8) + g1_duration = cell_cycle_model.GetStemCellG1Duration() + sg2m_duration = cell_cycle_model.GetSG2MDuration() + rnum = chaste.core.RandomNumberGenerator.Instance().ranf() + birth_time = -rnum * (g1_duration + sg2m_duration) + eachCell.SetBirthTime(birth_time) + + cell_population = chaste.cell_based.MeshBasedCellPopulation[2, 2](mesh, cells) + + cell_population.AddPopulationWriterVoronoiDataWriter() + + simulator = chaste.cell_based.OffLatticeSimulation[2, 2](cell_population) + simulator.SetOutputDirectory("Python/TestSpheroidTutorial") + simulator.SetEndTime(5.0) + + simulator.SetSamplingTimestepMultiple(100) + + force = chaste.cell_based.GeneralisedLinearSpringForce[2, 2]() + simulator.AddForce(force) + + pde = chaste.pde.CellwiseSourceEllipticPde[2](cell_population, -0.5) + + bc = chaste.pde.ConstBoundaryCondition[2](1.0) + is_neumann_bc = False + + # pde_modifier = chaste.cell_based.EllipticGrowingDomainPdeModifier[2](pde, bc, is_neumann_bc) + # pde_modifier.SetDependentVariableName("oxygen") + # simulator.AddSimulationModifier(pde_modifier) + + scene = chaste.visualization.VtkScene[2]() + scene.SetCellPopulation(cell_population) + scene.GetCellPopulationActorGenerator().SetColorByCellData(True) + scene.GetCellPopulationActorGenerator().SetDataLabel("oxygen") + scene.GetCellPopulationActorGenerator().SetShowCellCentres(True) + scene.GetCellPopulationActorGenerator().SetShowVoronoiMeshEdges(False) + # JUPYTER_SHOW_FIRST + + scene_modifier = chaste.cell_based.VtkSceneModifier[2]() + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(100) + simulator.AddSimulationModifier(scene_modifier) + + killer = chaste.cell_based.ApoptoticCellKiller[2](cell_population) + simulator.AddCellKiller(killer) + + scene.Start() + simulator.Solve() + + # JUPYTER_TEARDOWN + +if __name__ == "__main__": + unittest.main(verbosity=2) +``` diff --git a/pychaste/src/py/doc/tutorial/TensileTest.ipynb b/pychaste/src/py/doc/tutorial/TensileTest.ipynb new file mode 100644 index 0000000000..b9631bb6c2 --- /dev/null +++ b/pychaste/src/py/doc/tutorial/TensileTest.ipynb @@ -0,0 +1,304 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "16cafdae", + "metadata": {}, + "source": [ + "This tutorial is automatically generated from [TestPyTensileTestTutorial.py](https://github.com/Chaste/Chaste/blob/develop/pychaste/test/tutorial/TestPyTensileTestTutorial.py) at revision [4045f91a83f5](https://github.com/Chaste/Chaste/commit/4045f91a83f55dc4a97f2ca4f97b0c32f4e43a4a)." + ] + }, + { + "cell_type": "markdown", + "id": "1a596028", + "metadata": {}, + "source": [ + "\n", + "## Introduction\n", + "In this tutorial we will demonstrate a simulated tensile test on an epithelial sheet. This test\n", + "demonstrates:\n", + " * Working with vertex based off lattice populations\n", + " * Applying boundary conditions\n", + " * Working with forces\n", + " \n", + "## The Test\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "641d36a5", + "metadata": {}, + "outputs": [], + "source": [ + "import chaste # The PyChaste module\n", + "import chaste.cell_based # Contains cell populations\n", + "import chaste.mesh # Contains meshes\n", + "import chaste.visualization # Visualization tools\n", + "import numpy as np # Matrix tools" + ] + }, + { + "cell_type": "markdown", + "id": "a1ba193e", + "metadata": {}, + "source": [ + "### Test 1 - A 2D test\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "de290072", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.cell_based.SetupNotebookTest() # Set up the test" + ] + }, + { + "cell_type": "markdown", + "id": "7f4cd695", + "metadata": {}, + "source": [ + "First, we generate a vertex mesh using a HoneycombVertexMeshGenerator.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ab4d0090", + "metadata": {}, + "outputs": [], + "source": [ + "generator = chaste.mesh.HoneycombVertexMeshGenerator(5, 15)\n", + "mesh = generator.GetMesh()" + ] + }, + { + "cell_type": "markdown", + "id": "5898cf76", + "metadata": {}, + "source": [ + "Now set up the cells, again we want to avoid proliferation.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "56544d49", + "metadata": {}, + "outputs": [], + "source": [ + "differentiated_type = chaste.cell_based.DifferentiatedCellProliferativeType()\n", + "cell_generator = chaste.cell_based.CellsGenerator[\"UniformG1GenerationalCellCycleModel\", 2]()\n", + "cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), differentiated_type)" + ] + }, + { + "cell_type": "markdown", + "id": "58958f91", + "metadata": {}, + "source": [ + "Next, create the cell population\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9858e5f2", + "metadata": {}, + "outputs": [], + "source": [ + "cell_population = chaste.cell_based.VertexBasedCellPopulation[2](mesh, cells)" + ] + }, + { + "cell_type": "markdown", + "id": "62958208", + "metadata": {}, + "source": [ + "Pass the cell population into an `OffLatticeSimulation`, and set the output directory, output multiple and end time\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "adaf7447", + "metadata": {}, + "outputs": [], + "source": [ + "simulator = chaste.cell_based.OffLatticeSimulation[2, 2](cell_population)\n", + "simulator.SetOutputDirectory(\"Python/TestTensileTest\")\n", + "simulator.SetEndTime(1.0)\n", + "simulator.SetSamplingTimestepMultiple(1000)" + ] + }, + { + "cell_type": "markdown", + "id": "aab73a83", + "metadata": {}, + "source": [ + "Now create a force law\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "818200e6", + "metadata": {}, + "outputs": [], + "source": [ + "force = chaste.cell_based.NagaiHondaForce[2]()\n", + "simulator.AddForce(force)" + ] + }, + { + "cell_type": "markdown", + "id": "55a8a61f", + "metadata": {}, + "source": [ + "A `NagaiHondaForce` assumes that each cell has a target area. The target areas of cells are used to determine\n", + "pressure forces on each vertex and eventually determine the size of each cell in the simulation.\n", + "In order to assign target areas to cells and update them in each time step we add a `SimpleTargetAreaModifier`\n", + "to the simulation, which inherits from `AbstractTargetAreaModifier`.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8ee9400e", + "metadata": {}, + "outputs": [], + "source": [ + "growth_modifier = chaste.cell_based.SimpleTargetAreaModifier[2]()\n", + "simulator.AddSimulationModifier(growth_modifier)" + ] + }, + { + "cell_type": "markdown", + "id": "b80393e9", + "metadata": {}, + "source": [ + "For our tensile test we will fix the bottom of the sheet and subject the top to an applied displacement. We neglect\n", + "fixing lateral degress of freedom for simplicity, since we are using an over-damped mechanical model.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6cf82c51", + "metadata": {}, + "outputs": [], + "source": [ + "my_point = np.array([0.0, 0.0])\n", + "normal = np.array([0.0, -1.0])\n", + "bc = chaste.cell_based.AttractingPlaneBoundaryCondition[2, 2](cell_population, my_point, normal)\n", + "simulator.AddCellPopulationBoundaryCondition(bc)\n", + "point = np.array([0.0, 15.5])\n", + "normal = np.array([0.0, -1.0])\n", + "bc2 = chaste.cell_based.AttractingPlaneBoundaryCondition[2, 2](cell_population, point, normal)\n", + "simulator.AddCellPopulationBoundaryCondition(bc2)" + ] + }, + { + "cell_type": "markdown", + "id": "c208fd3d", + "metadata": {}, + "source": [ + "We want to displace our top boundary over time. We could write a custom boundary condition class to do this.\n", + "A more simple alternative is to modify the the position of the point describing our boundary plane in `bc2`\n", + "as the simulation progresses. As per earlier tutorials we make a new `SimulationModifier` class to do this.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3502c00d", + "metadata": {}, + "outputs": [], + "source": [ + "class BoundaryConditionModifier(chaste.cell_based.PythonSimulationModifier[2]):\n", + " \"\"\"Class for time varying boundary conditions\"\"\"\n", + " def __init__(self, boundary_condition):\n", + " self.boundary_condition = boundary_condition\n", + " self.original_location = boundary_condition.rGetPointOnPlane()\n", + " self.velocity = 0.5 # cell lengths per time\n", + " super(BoundaryConditionModifier, self).__init__()\n", + " def UpdateAtEndOfTimeStep(self, cell_population):\n", + " \"\"\"Move the boundary upwards at the specified velocity\"\"\"\n", + " total_time = chaste.cell_based.SimulationTime.Instance().GetTime()\n", + " new_location = [\n", + " self.original_location[0],\n", + " self.original_location[1] + self.velocity * total_time,\n", + " ]\n", + " self.boundary_condition.SetPointOnPlane(np.array(new_location))\n", + " def SetupSolve(self, cell_population, output_directory):\n", + " \"\"\"Make sure the cell population is in the correct state at the start of the simulation\"\"\"\n", + " cell_population.Update()\n", + "bc_modifier = BoundaryConditionModifier(bc2)\n", + "simulator.AddSimulationModifier(bc_modifier)" + ] + }, + { + "cell_type": "markdown", + "id": "29d8acb2", + "metadata": {}, + "source": [ + "PyChaste can do simple 3D rendering with VTK. We set up a `VtkScene` so that we can see the population\n", + "evovle in real time.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c5d60fcd", + "metadata": {}, + "outputs": [], + "source": [ + "scene = chaste.visualization.VtkScene[2]()\n", + "scene.SetCellPopulation(cell_population)\n", + "nb_manager = chaste.visualization.JupyterNotebookManager()\n", + "scene_modifier = chaste.cell_based.VtkSceneModifier[2]()\n", + "scene_modifier.SetVtkScene(scene)\n", + "scene_modifier.SetUpdateFrequency(1000)\n", + "simulator.AddSimulationModifier(scene_modifier)" + ] + }, + { + "cell_type": "markdown", + "id": "52bdf96d", + "metadata": {}, + "source": [ + "To run the simulation, we call `Solve()`.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8976a3c8", + "metadata": {}, + "outputs": [], + "source": [ + "scene.Start()\n", + "simulator.Solve()\n", + "chaste.cell_based.TearDownNotebookTest() # Tear down the test" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pychaste/src/py/doc/tutorial/TensileTest.md b/pychaste/src/py/doc/tutorial/TensileTest.md new file mode 100644 index 0000000000..aaf0faadfa --- /dev/null +++ b/pychaste/src/py/doc/tutorial/TensileTest.md @@ -0,0 +1,242 @@ +--- +title : "Tensile Test" +summary: "" +draft: false +images: [] +toc: true +layout: "single" +--- +This tutorial is automatically generated from [TestPyTensileTestTutorial.py](https://github.com/Chaste/Chaste/blob/develop/pychaste/test/tutorial/TestPyTensileTestTutorial.py) at revision [4045f91a83f5](https://github.com/Chaste/Chaste/commit/4045f91a83f55dc4a97f2ca4f97b0c32f4e43a4a). + +Note that the code is given in full at the bottom of the page. + +## Introduction +In this tutorial we will demonstrate a simulated tensile test on an epithelial sheet. This test +demonstrates: + * Working with vertex based off lattice populations + * Applying boundary conditions + * Working with forces + +## The Test + +```python +import unittest # Python testing framework + +import chaste # The PyChaste module +import chaste.cell_based # Contains cell populations +import chaste.mesh # Contains meshes +import chaste.visualization # Visualization tools +import numpy as np # Matrix tools + +class TestPyTensileTestTutorial(chaste.cell_based.AbstractCellBasedTestSuite): +``` +### Test 1 - A 2D test + +```python + def test_monolayer(self): + + # JUPYTER_SETUP +``` +First, we generate a vertex mesh using a HoneycombVertexMeshGenerator. + +```python + generator = chaste.mesh.HoneycombVertexMeshGenerator(5, 15) + mesh = generator.GetMesh() +``` +Now set up the cells, again we want to avoid proliferation. + +```python + differentiated_type = chaste.cell_based.DifferentiatedCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformG1GenerationalCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), differentiated_type) +``` +Next, create the cell population + +```python + cell_population = chaste.cell_based.VertexBasedCellPopulation[2](mesh, cells) +``` +Pass the cell population into an `OffLatticeSimulation`, and set the output directory, output multiple and end time + +```python + simulator = chaste.cell_based.OffLatticeSimulation[2, 2](cell_population) + simulator.SetOutputDirectory("Python/TestTensileTest") + simulator.SetEndTime(1.0) + simulator.SetSamplingTimestepMultiple(1000) +``` +Now create a force law + +```python + force = chaste.cell_based.NagaiHondaForce[2]() + simulator.AddForce(force) +``` +A `NagaiHondaForce` assumes that each cell has a target area. The target areas of cells are used to determine +pressure forces on each vertex and eventually determine the size of each cell in the simulation. +In order to assign target areas to cells and update them in each time step we add a `SimpleTargetAreaModifier` +to the simulation, which inherits from `AbstractTargetAreaModifier`. + +```python + growth_modifier = chaste.cell_based.SimpleTargetAreaModifier[2]() + simulator.AddSimulationModifier(growth_modifier) +``` +For our tensile test we will fix the bottom of the sheet and subject the top to an applied displacement. We neglect +fixing lateral degress of freedom for simplicity, since we are using an over-damped mechanical model. + +```python + my_point = np.array([0.0, 0.0]) + normal = np.array([0.0, -1.0]) + bc = chaste.cell_based.AttractingPlaneBoundaryCondition[2, 2](cell_population, my_point, normal) + simulator.AddCellPopulationBoundaryCondition(bc) + + point = np.array([0.0, 15.5]) + normal = np.array([0.0, -1.0]) + bc2 = chaste.cell_based.AttractingPlaneBoundaryCondition[2, 2](cell_population, point, normal) + simulator.AddCellPopulationBoundaryCondition(bc2) +``` +We want to displace our top boundary over time. We could write a custom boundary condition class to do this. +A more simple alternative is to modify the the position of the point describing our boundary plane in `bc2` +as the simulation progresses. As per earlier tutorials we make a new `SimulationModifier` class to do this. + +```python + class BoundaryConditionModifier(chaste.cell_based.PythonSimulationModifier[2]): + """Class for time varying boundary conditions""" + + def __init__(self, boundary_condition): + self.boundary_condition = boundary_condition + self.original_location = boundary_condition.rGetPointOnPlane() + self.velocity = 0.5 # cell lengths per time + super(BoundaryConditionModifier, self).__init__() + + def UpdateAtEndOfTimeStep(self, cell_population): + """Move the boundary upwards at the specified velocity""" + + total_time = chaste.cell_based.SimulationTime.Instance().GetTime() + new_location = [ + self.original_location[0], + self.original_location[1] + self.velocity * total_time, + ] + self.boundary_condition.SetPointOnPlane(np.array(new_location)) + + def SetupSolve(self, cell_population, output_directory): + """Make sure the cell population is in the correct state at the start of the simulation""" + + cell_population.Update() + + bc_modifier = BoundaryConditionModifier(bc2) + simulator.AddSimulationModifier(bc_modifier) +``` +PyChaste can do simple 3D rendering with VTK. We set up a `VtkScene` so that we can see the population +evovle in real time. + +```python + scene = chaste.visualization.VtkScene[2]() + scene.SetCellPopulation(cell_population) + # JUPYTER_SHOW_FIRST + + scene_modifier = chaste.cell_based.VtkSceneModifier[2]() + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(1000) + simulator.AddSimulationModifier(scene_modifier) +``` +To run the simulation, we call `Solve()`. + +```python + scene.Start() + simulator.Solve() + + # JUPYTER_TEARDOWN + +if __name__ == "__main__": + unittest.main(verbosity=2) +``` + +## Full code + +```python +import unittest # Python testing framework + +import chaste # The PyChaste module +import chaste.cell_based # Contains cell populations +import chaste.mesh # Contains meshes +import chaste.visualization # Visualization tools +import numpy as np # Matrix tools + +class TestPyTensileTestTutorial(chaste.cell_based.AbstractCellBasedTestSuite): + + def test_monolayer(self): + + # JUPYTER_SETUP + + generator = chaste.mesh.HoneycombVertexMeshGenerator(5, 15) + mesh = generator.GetMesh() + + differentiated_type = chaste.cell_based.DifferentiatedCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformG1GenerationalCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), differentiated_type) + + cell_population = chaste.cell_based.VertexBasedCellPopulation[2](mesh, cells) + + simulator = chaste.cell_based.OffLatticeSimulation[2, 2](cell_population) + simulator.SetOutputDirectory("Python/TestTensileTest") + simulator.SetEndTime(1.0) + simulator.SetSamplingTimestepMultiple(1000) + + force = chaste.cell_based.NagaiHondaForce[2]() + simulator.AddForce(force) + + growth_modifier = chaste.cell_based.SimpleTargetAreaModifier[2]() + simulator.AddSimulationModifier(growth_modifier) + + my_point = np.array([0.0, 0.0]) + normal = np.array([0.0, -1.0]) + bc = chaste.cell_based.AttractingPlaneBoundaryCondition[2, 2](cell_population, my_point, normal) + simulator.AddCellPopulationBoundaryCondition(bc) + + point = np.array([0.0, 15.5]) + normal = np.array([0.0, -1.0]) + bc2 = chaste.cell_based.AttractingPlaneBoundaryCondition[2, 2](cell_population, point, normal) + simulator.AddCellPopulationBoundaryCondition(bc2) + + class BoundaryConditionModifier(chaste.cell_based.PythonSimulationModifier[2]): + """Class for time varying boundary conditions""" + + def __init__(self, boundary_condition): + self.boundary_condition = boundary_condition + self.original_location = boundary_condition.rGetPointOnPlane() + self.velocity = 0.5 # cell lengths per time + super(BoundaryConditionModifier, self).__init__() + + def UpdateAtEndOfTimeStep(self, cell_population): + """Move the boundary upwards at the specified velocity""" + + total_time = chaste.cell_based.SimulationTime.Instance().GetTime() + new_location = [ + self.original_location[0], + self.original_location[1] + self.velocity * total_time, + ] + self.boundary_condition.SetPointOnPlane(np.array(new_location)) + + def SetupSolve(self, cell_population, output_directory): + """Make sure the cell population is in the correct state at the start of the simulation""" + + cell_population.Update() + + bc_modifier = BoundaryConditionModifier(bc2) + simulator.AddSimulationModifier(bc_modifier) + + scene = chaste.visualization.VtkScene[2]() + scene.SetCellPopulation(cell_population) + # JUPYTER_SHOW_FIRST + + scene_modifier = chaste.cell_based.VtkSceneModifier[2]() + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(1000) + simulator.AddSimulationModifier(scene_modifier) + + scene.Start() + simulator.Solve() + + # JUPYTER_TEARDOWN + +if __name__ == "__main__": + unittest.main(verbosity=2) +``` diff --git a/pychaste/src/py/doc/tutorial/VertexBasedCellSimulations.ipynb b/pychaste/src/py/doc/tutorial/VertexBasedCellSimulations.ipynb new file mode 100644 index 0000000000..714cdfcf3c --- /dev/null +++ b/pychaste/src/py/doc/tutorial/VertexBasedCellSimulations.ipynb @@ -0,0 +1,585 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "ff84a268", + "metadata": {}, + "source": [ + "This tutorial is automatically generated from [TestPyVertexBasedCellSimulationsTutorial.py](https://github.com/Chaste/Chaste/blob/develop/pychaste/test/tutorial/TestPyVertexBasedCellSimulationsTutorial.py) at revision [4045f91a83f5](https://github.com/Chaste/Chaste/commit/4045f91a83f55dc4a97f2ca4f97b0c32f4e43a4a)." + ] + }, + { + "cell_type": "markdown", + "id": "f2dd7573", + "metadata": {}, + "source": [ + "\n", + "## Introduction\n", + "In this tutorial we show how Chaste can be used to create, run and visualize vertex-based simulations.\n", + "Full details of the mechanical model proposed by T. Nagai and H. Honda (\"A dynamic cell model for the formation of epithelial tissues\",\n", + "Philosophical Magazine Part B 81:699-719).\n", + "\n", + "## The Test\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6c2a515c", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt # Plotting\n", + "import numpy as np # Matrix tools\n", + "import chaste # The PyChaste module\n", + "import chaste.cell_based # Contains cell populations\n", + "import chaste.mesh # Contains meshes\n", + "import chaste.visualization # Visualization tools\n", + "from chaste.cell_based import AbstractCellBasedTestSuite" + ] + }, + { + "cell_type": "markdown", + "id": "1c697ce1", + "metadata": {}, + "source": [ + "### Test 1 - A basic vertex-based simulation\n", + "In the first test, we run a simple vertex-based simulation, in which we create a monolayer of cells,\n", + "using a mutable vertex mesh. Each cell is assigned a stochastic cell-cycle model.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7b1828f1", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.cell_based.SetupNotebookTest() # Set up the test" + ] + }, + { + "cell_type": "markdown", + "id": "8f337f1f", + "metadata": {}, + "source": [ + "First, we generate a vertex mesh. To create a MutableVertexMesh, we can use the HoneycombVertexMeshGenerator.\n", + "This generates a honeycomb-shaped mesh, in which all nodes are equidistant. Here the first and second arguments\n", + "define the size of the mesh - we have chosen a mesh that is 2 elements (i.e. cells) wide, and 2 elements high.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "084453ed", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.core.OutputFileHandler(\"Python/TestVertexBasedCellSimulationsTutorial\")\n", + "generator = chaste.mesh.HoneycombVertexMeshGenerator(2, 2)\n", + "mesh = generator.GetMesh()" + ] + }, + { + "cell_type": "markdown", + "id": "21f1b811", + "metadata": {}, + "source": [ + "Having created a mesh, we now create a std::vector of CellPtrs. To do this, we use the CellsGenerator helper class,\n", + "which is templated over the type of cell model required\n", + "and the dimension. We create an empty vector of cells and pass this into the method along with the mesh.\n", + "The second argument represents the size of that the vector cells should become - one cell for each element,\n", + "the third argument specifies the proliferative type of the cell.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d6e9cd0e", + "metadata": {}, + "outputs": [], + "source": [ + "transit_type = chaste.cell_based.TransitCellProliferativeType()\n", + "cell_generator = chaste.cell_based.CellsGenerator[\"UniformG1GenerationalCellCycleModel\", 2]()\n", + "cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), transit_type)" + ] + }, + { + "cell_type": "markdown", + "id": "7b54d74b", + "metadata": {}, + "source": [ + "Now we have a mesh and a set of cells to go with it, we can create a CellPopulation.\n", + "In general, this class associates a collection of cells with a mesh. For this test, because we have a MutableVertexMesh,\n", + "we use a particular type of cell population called a VertexBasedCellPopulation.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "040d2906", + "metadata": {}, + "outputs": [], + "source": [ + "cell_population = chaste.cell_based.VertexBasedCellPopulation[2](mesh, cells)" + ] + }, + { + "cell_type": "markdown", + "id": "4369f20e", + "metadata": {}, + "source": [ + "We can set up a `VtkScene` to do a quick visualization of the population before running the analysis.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "77fb6883", + "metadata": {}, + "outputs": [], + "source": [ + "scene = chaste.visualization.VtkScene[2]()\n", + "scene.SetCellPopulation(cell_population)\n", + "nb_manager = chaste.visualization.JupyterNotebookManager()\n", + "nb_manager.vtk_show(scene, height=600)" + ] + }, + { + "cell_type": "markdown", + "id": "909581e2", + "metadata": {}, + "source": [ + "We then pass in the cell population into an `OffLatticeSimulation`, and set the output directory, output multiple and end time\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d9ddfe89", + "metadata": {}, + "outputs": [], + "source": [ + "simulator = chaste.cell_based.OffLatticeSimulation[2, 2](cell_population)\n", + "simulator.SetOutputDirectory(\"Python/TestVertexBasedCellSimulationsTutorial\")\n", + "simulator.SetEndTime(5.0)" + ] + }, + { + "cell_type": "markdown", + "id": "2cba9114", + "metadata": {}, + "source": [ + "For longer simulations, we may not want to output the results every time step.\n", + "In this case we can use the following method, to print results every 50 time steps instead.\n", + "As the default time step used by the simulator (for vertex based simulations), is 0.02 hours, this method\n", + "will cause the simulator to print results every 6 minutes (i.e. 0.1 hours).\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2da93466", + "metadata": {}, + "outputs": [], + "source": [ + "simulator.SetSamplingTimestepMultiple(50)" + ] + }, + { + "cell_type": "markdown", + "id": "d891293b", + "metadata": {}, + "source": [ + "We must now create one or more force laws, which determine the mechanics of the vertices of each cell in a cell population.\n", + "For this test, we use one force law, based on the Nagai-Honda mechanics, and pass it to the OffLatticeSimulation.\n", + "For a list of possible forces see subclasses of AbstractForce.\n", + "Note that some of these forces are not compatible with vertex-based simulations see the specific class documentation for details,\n", + "if you try to use an incompatible class then you will receive a warning.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5ec3ecdc", + "metadata": {}, + "outputs": [], + "source": [ + "force = chaste.cell_based.NagaiHondaForce[2]()\n", + "simulator.AddForce(force)" + ] + }, + { + "cell_type": "markdown", + "id": "f951b9b3", + "metadata": {}, + "source": [ + "A NagaiHondaForce assumes that each cell has a target area. The target areas of cells are used to determine\n", + "pressure forces on each vertex and eventually determine the size of each cell in the simulation.\n", + "In order to assign target areas to cells and update them in each time step we add a SimpleTargetAreaModifier\n", + "to the simulation, which inherits from AbstractTargetAreaModifier.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cb3352e0", + "metadata": {}, + "outputs": [], + "source": [ + "growth_modifier = chaste.cell_based.SimpleTargetAreaModifier[2]()\n", + "simulator.AddSimulationModifier(growth_modifier)" + ] + }, + { + "cell_type": "markdown", + "id": "e2af546f", + "metadata": {}, + "source": [ + "Save snapshot images of the population during the simulation\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f6bceed3", + "metadata": {}, + "outputs": [], + "source": [ + "scene_modifier = chaste.cell_based.VtkSceneModifier[2]()\n", + "scene_modifier.SetVtkScene(scene)\n", + "scene_modifier.SetUpdateFrequency(100)\n", + "simulator.AddSimulationModifier(scene_modifier)" + ] + }, + { + "cell_type": "markdown", + "id": "62b1b8c8", + "metadata": {}, + "source": [ + "To run the simulation, we call `Solve()`. We can again do a quick rendering of the population at the end of the simulation\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d187a368", + "metadata": {}, + "outputs": [], + "source": [ + "scene.Start()\n", + "simulator.Solve()\n", + "scene.End()" + ] + }, + { + "cell_type": "markdown", + "id": "3614028f", + "metadata": {}, + "source": [ + "The next two lines are for test purposes only and are not part of this tutorial.\n", + "If different simulation input parameters are being explored the lines should be removed.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dad835b4", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.cell_based.TearDownNotebookTest() # Tear down the test" + ] + }, + { + "cell_type": "markdown", + "id": "17c2dbc6", + "metadata": {}, + "source": [ + "### Test 2 - introducing periodicity, boundaries and cell killers\n", + "In the second test, we run a simple vertex-based simulation, in which we create a monolayer of cells in a periodic geometry,\n", + "using a cylindrical vertex mesh. We also include a fixed boundary which cells can't pass through and a cell killer which removes\n", + "cells once they leave a region. As before each cell is assigned a stochastic cell-cycle model.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e3469582", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.cell_based.SetupNotebookTest() # Set up the test" + ] + }, + { + "cell_type": "markdown", + "id": "5fa8d7f4", + "metadata": {}, + "source": [ + "First, we generate a periodic vertex mesh. To create a Cylindrical2dVertexMesh, we can use the CylindricalHoneycombVertexMeshGenerator.\n", + "This generates a honeycomb-shaped mesh, in which all nodes are equidistant and the right hand side is associated with the left hand side.\n", + "Here the first and second arguments define the size of the mesh - we have chosen a mesh that is\n", + "4 elements (i.e. cells) wide, and 4 elements high.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1a6ea007", + "metadata": {}, + "outputs": [], + "source": [ + "generator = chaste.mesh.CylindricalHoneycombVertexMeshGenerator(4, 4)\n", + "mesh = generator.GetCylindricalMesh()" + ] + }, + { + "cell_type": "markdown", + "id": "9ee8fbe7", + "metadata": {}, + "source": [ + "Having created a mesh, we now create a VectorSharedPtrCells. This is exactly the same as the above test.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a2ae4c4b", + "metadata": {}, + "outputs": [], + "source": [ + "transit_type = chaste.cell_based.TransitCellProliferativeType()\n", + "cell_generator = chaste.cell_based.CellsGenerator[\"UniformG1GenerationalCellCycleModel\", 2]()\n", + "cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), transit_type)" + ] + }, + { + "cell_type": "markdown", + "id": "c31c6c14", + "metadata": {}, + "source": [ + "Now we have a mesh and a set of cells to go with it, we can create a CellPopulation. This is also the same as in the above test.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0c61c14f", + "metadata": {}, + "outputs": [], + "source": [ + "cell_population = chaste.cell_based.VertexBasedCellPopulation[2](mesh, cells)" + ] + }, + { + "cell_type": "markdown", + "id": "8b2182fc", + "metadata": {}, + "source": [ + "We then pass in the cell population into an `OffLatticeSimulation`, and set the output directory, output multiple and end time\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2f65c0c1", + "metadata": {}, + "outputs": [], + "source": [ + "simulator = chaste.cell_based.OffLatticeSimulation[2, 2](cell_population)\n", + "simulator.SetOutputDirectory(\"Python/TestPeriodicVertexBasedCellPopulation\")\n", + "simulator.SetEndTime(1.0)\n", + "simulator.SetSamplingTimestepMultiple(50)" + ] + }, + { + "cell_type": "markdown", + "id": "7dd24c96", + "metadata": {}, + "source": [ + "We now make a pointer to an appropriate force and pass it to the OffLatticeSimulation.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0a2e3c66", + "metadata": {}, + "outputs": [], + "source": [ + "force = chaste.cell_based.NagaiHondaForce[2]()\n", + "simulator.AddForce(force)" + ] + }, + { + "cell_type": "markdown", + "id": "21b7f186", + "metadata": {}, + "source": [ + "We also make a pointer to the target area modifier and add it to the simulator.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10667482", + "metadata": {}, + "outputs": [], + "source": [ + "growth_modifier = chaste.cell_based.SimpleTargetAreaModifier[2]()\n", + "simulator.AddSimulationModifier(growth_modifier)" + ] + }, + { + "cell_type": "markdown", + "id": "6d37e086", + "metadata": {}, + "source": [ + "We now create one or more CellPopulationBoundaryConditions, which determine any conditions which each cell in a cell population must satisfy.\n", + "For this test, we use a PlaneBoundaryCondition, and pass it to the OffLatticeSimulation. For a list of possible boundary condition\n", + "see subclasses of AbstractCellPopulationBoundaryCondition.\n", + "Note that some of these boundary conditions are not compatible with vertex-based simulations see the specific class documentation\n", + "for details, if you try to use an incompatible class then you will receive a warning.\n", + "The first step is to define a point on the plane boundary and a normal to the plane.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b93b6bef", + "metadata": {}, + "outputs": [], + "source": [ + "point = np.array([0.0, 0.0])\n", + "normal = np.array([0.0, -1.0])" + ] + }, + { + "cell_type": "markdown", + "id": "b679cdcf", + "metadata": {}, + "source": [ + "We can now make a PlaneBoundaryCondition (passing the point and normal to the plane) and pass it to the OffLatticeSimulation.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "45798650", + "metadata": {}, + "outputs": [], + "source": [ + "bc = chaste.cell_based.PlaneBoundaryCondition[2, 2](cell_population, point, normal)\n", + "simulator.AddCellPopulationBoundaryCondition(bc)" + ] + }, + { + "cell_type": "markdown", + "id": "8a200966", + "metadata": {}, + "source": [ + "We now create one or more CellKillers, which determine how cells are removed from the simulation.\n", + "For this test, we use a PlaneBasedCellKiller, and pass it to the OffLatticeSimulation.\n", + "For a list of possible cell killers see subclasses of AbstractCellKiller.\n", + "The first step is to define a point on the plane boundary and a normal to the plane.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1d8325e2", + "metadata": {}, + "outputs": [], + "source": [ + "point = np.array([0.0, 3.0])\n", + "normal = np.array([0.0, 1.0])" + ] + }, + { + "cell_type": "markdown", + "id": "26ad4b82", + "metadata": {}, + "source": [ + "Finally we now make a PlaneBasedCellKiller (passing the point and normal to the plane) and pass it to the OffLatticeSimulation.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1ea457e0", + "metadata": {}, + "outputs": [], + "source": [ + "killer = chaste.cell_based.PlaneBasedCellKiller[2](cell_population, point, normal)\n", + "simulator.AddCellKiller(killer)" + ] + }, + { + "cell_type": "markdown", + "id": "bc769bcc", + "metadata": {}, + "source": [ + "To run the simulation, we call `Solve()`.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cc31e8cb", + "metadata": {}, + "outputs": [], + "source": [ + "simulator.Solve()" + ] + }, + { + "cell_type": "markdown", + "id": "9a76386c", + "metadata": {}, + "source": [ + "The next two lines are for test purposes only and are not part of this tutorial.\n", + "If different simulation input parameters are being explored the lines should be removed.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1611bdfb", + "metadata": {}, + "outputs": [], + "source": [ + "chaste.cell_based.TearDownNotebookTest() # Tear down the test" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pychaste/src/py/doc/tutorial/VertexBasedCellSimulations.md b/pychaste/src/py/doc/tutorial/VertexBasedCellSimulations.md new file mode 100644 index 0000000000..511c4228eb --- /dev/null +++ b/pychaste/src/py/doc/tutorial/VertexBasedCellSimulations.md @@ -0,0 +1,344 @@ +--- +title : "Vertex Based Cell Simulations" +summary: "" +draft: false +images: [] +toc: true +layout: "single" +--- +This tutorial is automatically generated from [TestPyVertexBasedCellSimulationsTutorial.py](https://github.com/Chaste/Chaste/blob/develop/pychaste/test/tutorial/TestPyVertexBasedCellSimulationsTutorial.py) at revision [4045f91a83f5](https://github.com/Chaste/Chaste/commit/4045f91a83f55dc4a97f2ca4f97b0c32f4e43a4a). + +Note that the code is given in full at the bottom of the page. + +## Introduction +In this tutorial we show how Chaste can be used to create, run and visualize vertex-based simulations. +Full details of the mechanical model proposed by T. Nagai and H. Honda ("A dynamic cell model for the formation of epithelial tissues", +Philosophical Magazine Part B 81:699-719). + +## The Test + +```python +import unittest # Python testing framework + +import matplotlib.pyplot as plt # Plotting +import numpy as np # Matrix tools + +import chaste # The PyChaste module +import chaste.cell_based # Contains cell populations +import chaste.mesh # Contains meshes +import chaste.visualization # Visualization tools + +from chaste.cell_based import AbstractCellBasedTestSuite + +class TestPyVertexBasedCellSimulationsTutorial(AbstractCellBasedTestSuite): +``` +### Test 1 - A basic vertex-based simulation +In the first test, we run a simple vertex-based simulation, in which we create a monolayer of cells, +using a mutable vertex mesh. Each cell is assigned a stochastic cell-cycle model. + +```python + def test_monolayer(self): + + # JUPYTER_SETUP +``` +First, we generate a vertex mesh. To create a MutableVertexMesh, we can use the HoneycombVertexMeshGenerator. +This generates a honeycomb-shaped mesh, in which all nodes are equidistant. Here the first and second arguments +define the size of the mesh - we have chosen a mesh that is 2 elements (i.e. cells) wide, and 2 elements high. + +```python + chaste.core.OutputFileHandler("Python/TestVertexBasedCellSimulationsTutorial") + generator = chaste.mesh.HoneycombVertexMeshGenerator(2, 2) + mesh = generator.GetMesh() +``` +Having created a mesh, we now create a std::vector of CellPtrs. To do this, we use the CellsGenerator helper class, +which is templated over the type of cell model required +and the dimension. We create an empty vector of cells and pass this into the method along with the mesh. +The second argument represents the size of that the vector cells should become - one cell for each element, +the third argument specifies the proliferative type of the cell. + +```python + transit_type = chaste.cell_based.TransitCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformG1GenerationalCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), transit_type) +``` +Now we have a mesh and a set of cells to go with it, we can create a CellPopulation. +In general, this class associates a collection of cells with a mesh. For this test, because we have a MutableVertexMesh, +we use a particular type of cell population called a VertexBasedCellPopulation. + +```python + cell_population = chaste.cell_based.VertexBasedCellPopulation[2](mesh, cells) +``` +We can set up a `VtkScene` to do a quick visualization of the population before running the analysis. + +```python + scene = chaste.visualization.VtkScene[2]() + scene.SetCellPopulation(cell_population) + # JUPYTER_SHOW_FIRST + scene.Start() # JUPYTER_SHOW +``` +We then pass in the cell population into an `OffLatticeSimulation`, and set the output directory, output multiple and end time + +```python + simulator = chaste.cell_based.OffLatticeSimulation[2, 2](cell_population) + simulator.SetOutputDirectory("Python/TestVertexBasedCellSimulationsTutorial") + simulator.SetEndTime(5.0) +``` +For longer simulations, we may not want to output the results every time step. +In this case we can use the following method, to print results every 50 time steps instead. +As the default time step used by the simulator (for vertex based simulations), is 0.02 hours, this method +will cause the simulator to print results every 6 minutes (i.e. 0.1 hours). + +```python + simulator.SetSamplingTimestepMultiple(50) +``` +We must now create one or more force laws, which determine the mechanics of the vertices of each cell in a cell population. +For this test, we use one force law, based on the Nagai-Honda mechanics, and pass it to the OffLatticeSimulation. +For a list of possible forces see subclasses of AbstractForce. +Note that some of these forces are not compatible with vertex-based simulations see the specific class documentation for details, +if you try to use an incompatible class then you will receive a warning. + +```python + force = chaste.cell_based.NagaiHondaForce[2]() + simulator.AddForce(force) +``` +A NagaiHondaForce assumes that each cell has a target area. The target areas of cells are used to determine +pressure forces on each vertex and eventually determine the size of each cell in the simulation. +In order to assign target areas to cells and update them in each time step we add a SimpleTargetAreaModifier +to the simulation, which inherits from AbstractTargetAreaModifier. + +```python + growth_modifier = chaste.cell_based.SimpleTargetAreaModifier[2]() + simulator.AddSimulationModifier(growth_modifier) +``` +Save snapshot images of the population during the simulation + +```python + scene_modifier = chaste.cell_based.VtkSceneModifier[2]() + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(100) + simulator.AddSimulationModifier(scene_modifier) +``` +To run the simulation, we call `Solve()`. We can again do a quick rendering of the population at the end of the simulation + +```python + scene.Start() + simulator.Solve() + scene.End() +``` +The next two lines are for test purposes only and are not part of this tutorial. +If different simulation input parameters are being explored the lines should be removed. + +```python + self.assertEqual(cell_population.GetNumRealCells(), 7) + self.assertAlmostEqual(chaste.cell_based.SimulationTime.Instance().GetTime(), 5.0, 6) + + # JUPYTER_TEARDOWN +``` +### Test 2 - introducing periodicity, boundaries and cell killers +In the second test, we run a simple vertex-based simulation, in which we create a monolayer of cells in a periodic geometry, +using a cylindrical vertex mesh. We also include a fixed boundary which cells can't pass through and a cell killer which removes +cells once they leave a region. As before each cell is assigned a stochastic cell-cycle model. + +```python + def test_periodic_monolayer(self): + + # JUPYTER_SETUP +``` +First, we generate a periodic vertex mesh. To create a Cylindrical2dVertexMesh, we can use the CylindricalHoneycombVertexMeshGenerator. +This generates a honeycomb-shaped mesh, in which all nodes are equidistant and the right hand side is associated with the left hand side. +Here the first and second arguments define the size of the mesh - we have chosen a mesh that is +4 elements (i.e. cells) wide, and 4 elements high. + +```python + generator = chaste.mesh.CylindricalHoneycombVertexMeshGenerator(4, 4) + mesh = generator.GetCylindricalMesh() +``` +Having created a mesh, we now create a VectorSharedPtrCells. This is exactly the same as the above test. + +```python + transit_type = chaste.cell_based.TransitCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformG1GenerationalCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), transit_type) +``` +Now we have a mesh and a set of cells to go with it, we can create a CellPopulation. This is also the same as in the above test. + +```python + cell_population = chaste.cell_based.VertexBasedCellPopulation[2](mesh, cells) +``` +We then pass in the cell population into an `OffLatticeSimulation`, and set the output directory, output multiple and end time + +```python + simulator = chaste.cell_based.OffLatticeSimulation[2, 2](cell_population) + simulator.SetOutputDirectory("Python/TestPeriodicVertexBasedCellPopulation") + simulator.SetEndTime(1.0) + simulator.SetSamplingTimestepMultiple(50) +``` +We now make a pointer to an appropriate force and pass it to the OffLatticeSimulation. + +```python + force = chaste.cell_based.NagaiHondaForce[2]() + simulator.AddForce(force) +``` +We also make a pointer to the target area modifier and add it to the simulator. + +```python + growth_modifier = chaste.cell_based.SimpleTargetAreaModifier[2]() + simulator.AddSimulationModifier(growth_modifier) +``` +We now create one or more CellPopulationBoundaryConditions, which determine any conditions which each cell in a cell population must satisfy. +For this test, we use a PlaneBoundaryCondition, and pass it to the OffLatticeSimulation. For a list of possible boundary condition +see subclasses of AbstractCellPopulationBoundaryCondition. +Note that some of these boundary conditions are not compatible with vertex-based simulations see the specific class documentation +for details, if you try to use an incompatible class then you will receive a warning. +The first step is to define a point on the plane boundary and a normal to the plane. + +```python + point = np.array([0.0, 0.0]) + normal = np.array([0.0, -1.0]) +``` +We can now make a PlaneBoundaryCondition (passing the point and normal to the plane) and pass it to the OffLatticeSimulation. + +```python + bc = chaste.cell_based.PlaneBoundaryCondition[2, 2](cell_population, point, normal) + simulator.AddCellPopulationBoundaryCondition(bc) +``` +We now create one or more CellKillers, which determine how cells are removed from the simulation. +For this test, we use a PlaneBasedCellKiller, and pass it to the OffLatticeSimulation. +For a list of possible cell killers see subclasses of AbstractCellKiller. +The first step is to define a point on the plane boundary and a normal to the plane. + +```python + point = np.array([0.0, 3.0]) + normal = np.array([0.0, 1.0]) +``` +Finally we now make a PlaneBasedCellKiller (passing the point and normal to the plane) and pass it to the OffLatticeSimulation. + +```python + killer = chaste.cell_based.PlaneBasedCellKiller[2](cell_population, point, normal) + simulator.AddCellKiller(killer) +``` +To run the simulation, we call `Solve()`. + +```python + simulator.Solve() +``` +The next two lines are for test purposes only and are not part of this tutorial. +If different simulation input parameters are being explored the lines should be removed. + +```python + self.assertEqual(cell_population.GetNumRealCells(), 12) + self.assertAlmostEqual(chaste.cell_based.SimulationTime.Instance().GetTime(), 1.0, 6) + + # JUPYTER_TEARDOWN + +if __name__ == "__main__": + unittest.main(verbosity=2) +``` + +## Full code + +```python +import unittest # Python testing framework + +import matplotlib.pyplot as plt # Plotting +import numpy as np # Matrix tools + +import chaste # The PyChaste module +import chaste.cell_based # Contains cell populations +import chaste.mesh # Contains meshes +import chaste.visualization # Visualization tools + +from chaste.cell_based import AbstractCellBasedTestSuite + +class TestPyVertexBasedCellSimulationsTutorial(AbstractCellBasedTestSuite): + def test_monolayer(self): + + # JUPYTER_SETUP + + chaste.core.OutputFileHandler("Python/TestVertexBasedCellSimulationsTutorial") + generator = chaste.mesh.HoneycombVertexMeshGenerator(2, 2) + mesh = generator.GetMesh() + + transit_type = chaste.cell_based.TransitCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformG1GenerationalCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), transit_type) + + cell_population = chaste.cell_based.VertexBasedCellPopulation[2](mesh, cells) + + scene = chaste.visualization.VtkScene[2]() + scene.SetCellPopulation(cell_population) + # JUPYTER_SHOW_FIRST + scene.Start() # JUPYTER_SHOW + + simulator = chaste.cell_based.OffLatticeSimulation[2, 2](cell_population) + simulator.SetOutputDirectory("Python/TestVertexBasedCellSimulationsTutorial") + simulator.SetEndTime(5.0) + + simulator.SetSamplingTimestepMultiple(50) + + force = chaste.cell_based.NagaiHondaForce[2]() + simulator.AddForce(force) + + growth_modifier = chaste.cell_based.SimpleTargetAreaModifier[2]() + simulator.AddSimulationModifier(growth_modifier) + + scene_modifier = chaste.cell_based.VtkSceneModifier[2]() + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(100) + simulator.AddSimulationModifier(scene_modifier) + + scene.Start() + simulator.Solve() + scene.End() + + self.assertEqual(cell_population.GetNumRealCells(), 7) + self.assertAlmostEqual(chaste.cell_based.SimulationTime.Instance().GetTime(), 5.0, 6) + + # JUPYTER_TEARDOWN + + def test_periodic_monolayer(self): + + # JUPYTER_SETUP + + generator = chaste.mesh.CylindricalHoneycombVertexMeshGenerator(4, 4) + mesh = generator.GetCylindricalMesh() + + transit_type = chaste.cell_based.TransitCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformG1GenerationalCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), transit_type) + + cell_population = chaste.cell_based.VertexBasedCellPopulation[2](mesh, cells) + + simulator = chaste.cell_based.OffLatticeSimulation[2, 2](cell_population) + simulator.SetOutputDirectory("Python/TestPeriodicVertexBasedCellPopulation") + simulator.SetEndTime(1.0) + simulator.SetSamplingTimestepMultiple(50) + + force = chaste.cell_based.NagaiHondaForce[2]() + simulator.AddForce(force) + + growth_modifier = chaste.cell_based.SimpleTargetAreaModifier[2]() + simulator.AddSimulationModifier(growth_modifier) + + point = np.array([0.0, 0.0]) + normal = np.array([0.0, -1.0]) + + bc = chaste.cell_based.PlaneBoundaryCondition[2, 2](cell_population, point, normal) + simulator.AddCellPopulationBoundaryCondition(bc) + + point = np.array([0.0, 3.0]) + normal = np.array([0.0, 1.0]) + + killer = chaste.cell_based.PlaneBasedCellKiller[2](cell_population, point, normal) + simulator.AddCellKiller(killer) + + simulator.Solve() + + self.assertEqual(cell_population.GetNumRealCells(), 12) + self.assertAlmostEqual(chaste.cell_based.SimulationTime.Instance().GetTime(), 1.0, 6) + + # JUPYTER_TEARDOWN + +if __name__ == "__main__": + unittest.main(verbosity=2) +``` diff --git a/pychaste/src/py/pyproject.toml b/pychaste/src/py/pyproject.toml new file mode 100644 index 0000000000..042c6cdf5f --- /dev/null +++ b/pychaste/src/py/pyproject.toml @@ -0,0 +1,34 @@ +# Copyright (c) 2005-2024, University of Oxford. +# All rights reserved. + +# University of Oxford means the Chancellor, Masters and Scholars of the +# University of Oxford, having an administrative office at Wellington +# Square, Oxford OX1 2JD, UK. + +# This file is part of Chaste. + +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of the University of Oxford nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. + +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" diff --git a/pychaste/src/py/setup.cfg b/pychaste/src/py/setup.cfg new file mode 100644 index 0000000000..f51ebf313a --- /dev/null +++ b/pychaste/src/py/setup.cfg @@ -0,0 +1,64 @@ +# Copyright (c) 2005-2024, University of Oxford. +# All rights reserved. + +# University of Oxford means the Chancellor, Masters and Scholars of the +# University of Oxford, having an administrative office at Wellington +# Square, Oxford OX1 2JD, UK. + +# This file is part of Chaste. + +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of the University of Oxford nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. + +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +[metadata] +name = chaste +version = 2024.1 +author = Chaste Developers +author_email = chaste-users@maillist.ox.ac.uk +description = Python bindings for Chaste, a general purpose simulation package for computational biology. +keywords = cancer, developmental biology, electrophysiology, scientific +license = BSD-3-Clause +classifiers = + Development Status :: 4 - Beta + Intended Audience :: Science/Research + Topic :: Scientific/Engineering + Operating System :: POSIX + License :: OSI Approved :: BSD License + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 + Programming Language :: Python :: Implementation :: CPython + +[options] +zip_safe = False +include_package_data = True +packages = find: +python_requires = >=3.8 +install_requires = + matplotlib + numpy + xvfbwrapper + +[options.packages.find] +exclude = + doc diff --git a/pychaste/src/py/setup.py b/pychaste/src/py/setup.py new file mode 100644 index 0000000000..4b986b69d2 --- /dev/null +++ b/pychaste/src/py/setup.py @@ -0,0 +1,44 @@ +"""Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +from setuptools import setup, Distribution + + +class BinaryDistribution(Distribution): + def is_pure(self): + return False + + def has_ext_modules(self): + return True + + +setup(distclass=BinaryDistribution) diff --git a/pychaste/test/CMakeLists.txt b/pychaste/test/CMakeLists.txt new file mode 100644 index 0000000000..7a5e610bc3 --- /dev/null +++ b/pychaste/test/CMakeLists.txt @@ -0,0 +1,40 @@ +# Copyright (c) 2005-2024, University of Oxford. +# All rights reserved. +# +# University of Oxford means the Chancellor, Masters and Scholars of the +# University of Oxford, having an administrative office at Wellington +# Square, Oxford OX1 2JD, UK. +# +# This file is part of Chaste. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of the University of Oxford nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +if(NOT Chaste_ENABLE_PYCHASTE) + return() +endif() + +project(testpychaste) +chaste_do_test_component(pychaste) + +set_tests_properties(TestPyImports PROPERTIES WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/pychaste") +set_tests_properties(TestPyWrapperChanges PROPERTIES WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/pychaste") diff --git a/pychaste/test/ContinuousTestPack.txt b/pychaste/test/ContinuousTestPack.txt new file mode 100644 index 0000000000..4b5f981ba6 --- /dev/null +++ b/pychaste/test/ContinuousTestPack.txt @@ -0,0 +1,27 @@ +cell_based/TestPyCaBasedCellPopulation.py +cell_based/TestPyMeshBasedCellPopulation.py +cell_based/TestPyNodeBasedCellPopulation.py +cell_based/TestPyVertexBasedCellPopulation.py +core/TestPyFileFinder.py +core/TestPyImports.py +core/TestPyOutputFileHandler.py +core/TestPyPetscTools.py +core/TestPyRandomNumberGenerator.py +core/TestPyTimer.py +core/TestPyVersion.py +core/TestPyWrapperChanges.py +mesh/TestPyChastePoint.py +mesh/TestPyPottsMesh.py +tutorial/TestPyCellSortingTutorial.py +tutorial/TestPyImmersedBoundaryTutorial.py +tutorial/TestPyMeshBasedCellSimulationsTutorial.py +tutorial/TestPyNodeBasedCellSimulationsTutorial.py +tutorial/TestPyPottsBasedCellSimulationsTutorial.py +tutorial/TestPyScratchAssayTutorial.py +tutorial/TestPySpheroidTutorial.py +tutorial/TestPyTensileTestTutorial.py +tutorial/TestPyVertexBasedCellSimulationsTutorial.py +visualization/TestPyJupyterNotebookManager.py +visualization/TestVtkSceneWithCaBased.hpp +visualization/TestVtkSceneWithMeshBased.hpp +visualization/TestVtkSceneWithPottsBased.hpp diff --git a/pychaste/test/cell_based/TestPyCaBasedCellPopulation.py b/pychaste/test/cell_based/TestPyCaBasedCellPopulation.py new file mode 100644 index 0000000000..0ffb98dab9 --- /dev/null +++ b/pychaste/test/cell_based/TestPyCaBasedCellPopulation.py @@ -0,0 +1,74 @@ +"""Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import unittest + +import chaste.cell_based +import chaste.mesh +import chaste.visualization + + +class TestPyCaBasedCellPopulation(chaste.cell_based.AbstractCellBasedTestSuite): + + def test_construct(self): + work_dir = "Python/TestCaBasedPopulationPython" + file_handler = chaste.core.OutputFileHandler(work_dir) + generator = chaste.mesh.PottsMeshGenerator[3](10, 0, 0, 10, 0, 0, 10, 0, 0) + mesh = generator.GetMesh() + + locs = range(5) + cell_generator = chaste.cell_based.CellsGenerator["UniformCellCycleModel", 3]() + cells = cell_generator.GenerateBasic(len(locs)) + cell_population = chaste.cell_based.CaBasedCellPopulation[3](mesh, cells, locs) + + scene = chaste.visualization.VtkScene[3]() + scene.SetCellPopulation(cell_population) + scene.SetSaveAsImages(True) + scene.SetOutputFilePath( + file_handler.GetOutputDirectoryFullPath() + "/cell_population" + ) + scene.GetCellPopulationActorGenerator().SetShowPottsMeshEdges(True) + scene.GetCellPopulationActorGenerator().SetVolumeOpacity(1.0) + scene_modifier = chaste.cell_based.VtkSceneModifier[3]() + scene_modifier.SetVtkScene(scene) + scene.Start() + + simulator = chaste.cell_based.OnLatticeSimulation[3](cell_population) + simulator.SetOutputDirectory(work_dir) + simulator.SetDt(10.0) + simulator.SetEndTime(300.0) + simulator.AddSimulationModifier(scene_modifier) + simulator.Solve() + + +if __name__ == "__main__": + unittest.main() diff --git a/pychaste/test/cell_based/TestPyMeshBasedCellPopulation.py b/pychaste/test/cell_based/TestPyMeshBasedCellPopulation.py new file mode 100644 index 0000000000..018430e292 --- /dev/null +++ b/pychaste/test/cell_based/TestPyMeshBasedCellPopulation.py @@ -0,0 +1,86 @@ +"""Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import unittest + +import chaste.cell_based +import chaste.mesh +import chaste.visualization + + +class TestPyMeshBasedCellPopulation(chaste.cell_based.AbstractCellBasedTestSuite): + + def test_construct(self): + + file_handler = chaste.core.OutputFileHandler( + "Python/TestMeshBasedCellPopulation" + ) + + mesh_generator = chaste.mesh.HoneycombMeshGenerator(2, 2) + mesh = mesh_generator.GetMesh() + + # Make the cells + proliferative_type = chaste.cell_based.DefaultCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom( + mesh.GetNumNodes(), proliferative_type + ) + + # Make the cell population + cell_population = chaste.cell_based.MeshBasedCellPopulation[2, 2](mesh, cells) + cell_population.AddPopulationWriterVoronoiDataWriter() + + # Set up the visualizer + scene = chaste.visualization.VtkScene[2]() + scene.SetCellPopulation(cell_population) + scene.SetSaveAsAnimation(True) + scene.SetOutputFilePath( + file_handler.GetOutputDirectoryFullPath() + "/cell_population" + ) + + modifier = chaste.cell_based.VtkSceneModifier[2]() + modifier.SetVtkScene(scene) + + # Set up the simulation + simulator = chaste.cell_based.OffLatticeSimulation[2, 2](cell_population) + simulator.SetOutputDirectory("Python/TestMeshBasedCellPopulation") + simulator.SetEndTime(5.0) + simulator.SetSamplingTimestepMultiple(12) + + force = chaste.cell_based.GeneralisedLinearSpringForce[2, 2]() + simulator.AddForce(force) + simulator.AddSimulationModifier(modifier) + simulator.Solve() + + +if __name__ == "__main__": + unittest.main() diff --git a/pychaste/test/cell_based/TestPyNodeBasedCellPopulation.py b/pychaste/test/cell_based/TestPyNodeBasedCellPopulation.py new file mode 100644 index 0000000000..b8e6c2ffc5 --- /dev/null +++ b/pychaste/test/cell_based/TestPyNodeBasedCellPopulation.py @@ -0,0 +1,86 @@ +"""Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import unittest + +import chaste.cell_based +import chaste.mesh +import chaste.visualization + + +class TestPyNodeBasedCellPopulation(chaste.cell_based.AbstractCellBasedTestSuite): + + def test_construct(self): + + file_handler = chaste.core.OutputFileHandler( + "Python/TestNodeBasedCellPopulation" + ) + + mesh_generator = chaste.mesh.PottsMeshGenerator[3](10, 0, 0, 10, 0, 0, 5, 0, 0) + mesh = mesh_generator.GetMesh() + + nodes_only_mesh = chaste.mesh.NodesOnlyMesh[3]() + nodes_only_mesh.ConstructNodesWithoutMesh(mesh, 1.5) + + # Make the cells + cell_generator = chaste.cell_based.CellsGenerator["UniformCellCycleModel", 2]() + cells = cell_generator.GenerateBasic(500) + + # Make the cell population + cell_population = chaste.cell_based.NodeBasedCellPopulation[3]( + nodes_only_mesh, cells + ) + + # Set up the visualizer + scene = chaste.visualization.VtkScene[3]() + scene.SetCellPopulation(cell_population) + scene.SetSaveAsAnimation(True) + scene.SetOutputFilePath( + file_handler.GetOutputDirectoryFullPath() + "/cell_population" + ) + + modifier = chaste.cell_based.VtkSceneModifier[3]() + modifier.SetVtkScene(scene) + + # Set up the simulation + simulator = chaste.cell_based.OffLatticeSimulation[3, 3](cell_population) + simulator.SetOutputDirectory("Python/TestNodeBasedCellPopulation") + simulator.SetEndTime(4.0) + simulator.SetDt(1.0) + simulator.SetSamplingTimestepMultiple(1) + simulator.AddSimulationModifier(modifier) + + simulator.Solve() + + +if __name__ == "__main__": + unittest.main() diff --git a/pychaste/test/cell_based/TestPyVertexBasedCellPopulation.py b/pychaste/test/cell_based/TestPyVertexBasedCellPopulation.py new file mode 100644 index 0000000000..f8919b4fdd --- /dev/null +++ b/pychaste/test/cell_based/TestPyVertexBasedCellPopulation.py @@ -0,0 +1,91 @@ +"""Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import unittest + +import chaste +import chaste.cell_based +import chaste.mesh +import chaste.visualization + + +class TestPyVertexBasedCellPopulation(chaste.cell_based.AbstractCellBasedTestSuite): + + def test_construct(self): + + file_handler = chaste.core.OutputFileHandler( + "Python/TestVertexBasedCellPopulation" + ) + + # Set up the mesh + mesh_generator = chaste.mesh.HoneycombVertexMeshGenerator(2, 2) + mesh = mesh_generator.GetMesh() + + # Make the cells + proliferative_type = chaste.cell_based.DefaultCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom( + mesh.GetNumElements(), proliferative_type + ) + + # Make the cell population + cell_population = chaste.cell_based.VertexBasedCellPopulation[2](mesh, cells) + + # Set up the visualizer + scene = chaste.visualization.VtkScene[2]() + scene.SetCellPopulation(cell_population) + scene.SetSaveAsAnimation(True) + scene.SetOutputFilePath( + file_handler.GetOutputDirectoryFullPath() + "/cell_population" + ) + + modifier = chaste.cell_based.VtkSceneModifier[2]() + modifier.SetVtkScene(scene) + + force = chaste.cell_based.NagaiHondaForce[2]() + target_area_modifier = chaste.cell_based.SimpleTargetAreaModifier[2]() + target_area_modifier.SetGrowthDuration(1.0) + + # Set up the simulation + simulator = chaste.cell_based.OffLatticeSimulation[2, 2](cell_population) + simulator.SetOutputDirectory("Python/TestVertexBasedCellPopulation") + simulator.SetEndTime(0.2) + simulator.AddForce(force) + simulator.SetSamplingTimestepMultiple(200) + simulator.AddSimulationModifier(modifier) + simulator.AddSimulationModifier(target_area_modifier) + + simulator.Solve() + + +if __name__ == "__main__": + unittest.main() diff --git a/pychaste/test/core/TestPyFileFinder.py b/pychaste/test/core/TestPyFileFinder.py new file mode 100644 index 0000000000..d89dcee6a5 --- /dev/null +++ b/pychaste/test/core/TestPyFileFinder.py @@ -0,0 +1,51 @@ +"""Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import unittest + +import chaste.core + + +class TestPyFileFinder(unittest.TestCase): + + def test_find_file(self): + + file_finder = chaste.core.FileFinder( + "pychaste/test/data/find_me.txt", + chaste.core.RelativeTo.ChasteSourceRoot, + ) + self.assertEqual(file_finder.GetLeafName(), "find_me.txt") + self.assertTrue(file_finder.IsFile()) + + +if __name__ == "__main__": + unittest.main() diff --git a/pychaste/test/core/TestPyImports.py b/pychaste/test/core/TestPyImports.py new file mode 100644 index 0000000000..c908b5d220 --- /dev/null +++ b/pychaste/test/core/TestPyImports.py @@ -0,0 +1,109 @@ +"""Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import os +import re +import unittest + +import chaste +import chaste.cell_based +import chaste.core +import chaste.mesh +import chaste.ode +import chaste.pde +import chaste.visualization +from chaste import * +from chaste._syntax import TemplateClassDict +from chaste.cell_based import * +from chaste.core import * +from chaste.mesh import * +from chaste.ode import * +from chaste.pde import * +from chaste.visualization import * + + +class TestPyImports(unittest.TestCase): + + def test_imports(self): + module_wrapper = os.path.abspath("wrappers/all/_pychaste_all.main.cppwg.cpp") + class_names = [] + + class_regex = re.compile(r"^\s*register_(\w+)_class\(m\);\s*$") + with open(module_wrapper, "r") as f: + lines = f.readlines() + + for line in lines: + class_match = class_regex.match(line) + if class_match: + class_name = class_match.groups(0)[0] + if not class_name.startswith("Abstract"): + class_names.append(class_name) + + for class_name in class_names: + # Check that wrapped classes are imported in PyChaste + clas = globals().get(class_name, None) + self.assertTrue( + clas is not None, + f"\nClass {class_name} is wrapped but not imported in PyChaste" + "\n-- to fix, add an import in the relevant PyChaste module" + " e.g. to add to pychaste core, update" + " pychaste/src/py/chaste/core/__init__.py", + ) + + # Extra checks for templated classes + if "_" in class_name: + template_name, *args = class_name.split("_") + template = globals().get(template_name, None) + + # Templated classes should have a TemplateClassDict entry + defined = template and isinstance(template, TemplateClassDict) + self.assertTrue( + defined, + f"\nClass {class_name} does not have a corresponding TemplateClassDict entry" + "\n-- to fix, add an entry in the relevant PyChaste module" + " e.g. to add to pychaste core, update" + " pychaste/src/py/chaste/core/__init__.py", + ) + + # Check that the TemplateClassDict returns the correct type + self.assertEqual( + template[tuple(args)], + clas, + f"\nTemplatedClass {template_name}[{args}] does not return {class_name}" + "\n-- to fix, check the TemplateClassDict initialisation for {class_name}" + " in the relevant PyChaste module e.g. if the class is in pychaste" + " core, check pychaste/src/py/chaste/core/__init__.py", + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/pychaste/test/core/TestPyOutputFileHandler.py b/pychaste/test/core/TestPyOutputFileHandler.py new file mode 100644 index 0000000000..64da99b4e5 --- /dev/null +++ b/pychaste/test/core/TestPyOutputFileHandler.py @@ -0,0 +1,51 @@ +"""Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import unittest + +import chaste.core + + +class TestPyOutputFileHandler(unittest.TestCase): + + def test_create_directory(self): + + file_handler = chaste.core.OutputFileHandler("TestOutputFileHandler", True) + chaste_output_dir = file_handler.GetChasteTestOutputDirectory() + self.assertEqual( + chaste_output_dir + "TestOutputFileHandler/", + file_handler.GetOutputDirectoryFullPath(), + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/pychaste/test/core/TestPyPetscTools.py b/pychaste/test/core/TestPyPetscTools.py new file mode 100644 index 0000000000..80e7fe3932 --- /dev/null +++ b/pychaste/test/core/TestPyPetscTools.py @@ -0,0 +1,55 @@ +"""Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import unittest + +import chaste + + +class TestPyPetscTools(unittest.TestCase): + + def test_some_vecs(self): + + # Make a Petsc Vec + local = [1.0, 2.0, 3.0] + vec = chaste.core.PetscTools.CreateVec(local) + + # Get a Replicatable Vec + replicatable_vec = chaste.core.ReplicatableVector(vec) + + self.assertAlmostEqual(replicatable_vec[0], 1.0, 6) + self.assertAlmostEqual(replicatable_vec[1], 2.0, 6) + self.assertAlmostEqual(replicatable_vec[2], 3.0, 6) + + +if __name__ == "__main__": + unittest.main() diff --git a/pychaste/test/core/TestPyRandomNumberGenerator.py b/pychaste/test/core/TestPyRandomNumberGenerator.py new file mode 100644 index 0000000000..b29d9228ff --- /dev/null +++ b/pychaste/test/core/TestPyRandomNumberGenerator.py @@ -0,0 +1,53 @@ +"""Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import unittest + +import chaste.core + + +class TestPyRandomNumberGenerator(unittest.TestCase): + + def test_make_some_numbers(self): + + rng = chaste.core.RandomNumberGenerator.Instance() + rng.Reseed(1234) + self.assertAlmostEqual(rng.ranf(), 0.191519450163, 5) + + rng.Destroy() + rng2 = chaste.core.RandomNumberGenerator.Instance() + rng2.Reseed(1234) + self.assertAlmostEqual(rng2.ranf(), 0.191519450163, 5) + + +if __name__ == "__main__": + unittest.main() diff --git a/pychaste/test/core/TestPyTimer.py b/pychaste/test/core/TestPyTimer.py new file mode 100644 index 0000000000..63fb603fb9 --- /dev/null +++ b/pychaste/test/core/TestPyTimer.py @@ -0,0 +1,47 @@ +"""Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import unittest + +import chaste.core + + +class TestPyTimer(unittest.TestCase): + + def test_start_stop(self): + + chaste.core.Timer.Reset() + chaste.core.Timer.Print("This Happened At: ") + + +if __name__ == "__main__": + unittest.main() diff --git a/pychaste/test/core/TestPyVersion.py b/pychaste/test/core/TestPyVersion.py new file mode 100644 index 0000000000..eb10ebbfbd --- /dev/null +++ b/pychaste/test/core/TestPyVersion.py @@ -0,0 +1,48 @@ +"""Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import unittest + +import chaste.core + + +class TestPyVersion(unittest.TestCase): + + def test_output_version_info(self): + + print(chaste.core.ChasteBuildInfo.GetLicenceText()) + + print(chaste.core.ChasteBuildInfo.GetRootDir()) + + +if __name__ == "__main__": + unittest.main() diff --git a/pychaste/test/core/TestPyWrapperChanges.py b/pychaste/test/core/TestPyWrapperChanges.py new file mode 100644 index 0000000000..1a6b9ccb69 --- /dev/null +++ b/pychaste/test/core/TestPyWrapperChanges.py @@ -0,0 +1,72 @@ +"""Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import os +import re +import unittest + + +class TestPyWrapperChanges(unittest.TestCase): + """Test that the generated wrappers are up to date.""" + + def test_unwrapped_classes(self) -> None: + """Find unwrapped classes.""" + + log_file = os.path.abspath("cppwg.log") + self.assertTrue( + os.path.isfile(log_file), + "Cannot find wrapper generator logs: " + log_file, + ) + + unknown_classes = [] + with open(log_file, "r") as lf: + for line in lf: + if ( + "Unknown class" in line + and "/cell_based/src/" in line + and not "/cell_based/src/fortests/" in line + and not re.search(r"Unknown class guid_defined<.*>", line) + and not re.search(r"Unknown class pack<.*>", line) + and not re.search(r"Unknown class [\w]*Iterator\b", line) + ): + unknown_classes.append(line) + self.assertEqual( + len(unknown_classes), + 0, + "\n" + "".join(unknown_classes) + "Found unknown classes" + "\n-- to wrap, add relevant entries to config.yaml. " + "\n-- to exclude from wrapping, add to config.yaml with the `exclude` option.", + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/pychaste/test/data/find_me.txt b/pychaste/test/data/find_me.txt new file mode 100644 index 0000000000..3d246f6d38 --- /dev/null +++ b/pychaste/test/data/find_me.txt @@ -0,0 +1 @@ +For testing the file finder \ No newline at end of file diff --git a/pychaste/test/mesh/TestPyChastePoint.py b/pychaste/test/mesh/TestPyChastePoint.py new file mode 100644 index 0000000000..3f081fb6b0 --- /dev/null +++ b/pychaste/test/mesh/TestPyChastePoint.py @@ -0,0 +1,63 @@ +"""Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import unittest + +import chaste +import chaste.mesh +import numpy as np + + +class TestPyChastePoint(unittest.TestCase): + + def test_construct(self): + + point = chaste.mesh.ChastePoint[3](1.0, 2.0, 3.0) + loc = point.rGetLocation() + self.assertAlmostEqual(loc[0], 1.0, 2) + self.assertAlmostEqual(loc[1], 2.0, 2) + self.assertAlmostEqual(loc[2], 3.0, 2) + + point = chaste.mesh.ChastePoint[3](np.array([4.0, 5.0, 6.0])) + loc = point.rGetLocation() + self.assertAlmostEqual(loc[0], 4.0, 2) + self.assertAlmostEqual(loc[1], 5.0, 2) + self.assertAlmostEqual(loc[2], 6.0, 2) + + point = chaste.mesh.ChastePoint[2](np.array([4.0, 5.0])) + loc = point.rGetLocation() + self.assertAlmostEqual(loc[0], 4.0, 2) + self.assertAlmostEqual(loc[1], 5.0, 2) + + +if __name__ == "__main__": + unittest.main() diff --git a/pychaste/test/mesh/TestPyPottsMesh.py b/pychaste/test/mesh/TestPyPottsMesh.py new file mode 100644 index 0000000000..6f2471f585 --- /dev/null +++ b/pychaste/test/mesh/TestPyPottsMesh.py @@ -0,0 +1,48 @@ +"""Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import unittest + +import chaste +import chaste.mesh + + +class TestPyPottsMesh(unittest.TestCase): + + def test_construct(self): + generator = chaste.mesh.PottsMeshGenerator[3](10, 0, 0, 10, 0, 0, 10, 0, 0) + mesh = generator.GetMesh() + self.assertEqual(mesh.GetNumNodes(), 1000) + + +if __name__ == "__main__": + unittest.main() diff --git a/pychaste/test/tutorial/TestPyCellSortingTutorial.py b/pychaste/test/tutorial/TestPyCellSortingTutorial.py new file mode 100644 index 0000000000..19a96a5303 --- /dev/null +++ b/pychaste/test/tutorial/TestPyCellSortingTutorial.py @@ -0,0 +1,147 @@ +"""Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +# ifndef +# define TRIGGER_WIKI + +## ## Introduction +## This test is a demonstration of cell sorting using a Cellular Potts based framework. +## It shows: +## * How to set up a Potts simulation +## * Working with labels +## +## ## The Test + +import unittest # Python testing framework + +import chaste # The PyChaste module +import chaste.cell_based # Contains cell populations +import chaste.mesh # Contains meshes +import chaste.visualization # Visualization tools +import matplotlib.pyplot as plt # Plotting +import numpy as np # Matrix tools + + +class TestPyCellSortingTutorial(chaste.cell_based.AbstractCellBasedTestSuite): + + ## ### Test 1 - Cell sorting + ## The next test generates a collection of cells, there are two types of cells, labelled ones and non labelled ones, + ## there is differential adhesion between the cell types. For the parameters specified, the cells sort into separate types. + + def test_potts_monolayer_cell_sorting(self): + + # JUPYTER_SETUP + + ## First, we generate a `Potts` mesh. To create a `PottsMesh`, we can use the `PottsMeshGenerator`. + ## This generates a regular square-shaped mesh, in which all elements are the same size. + ## We have chosen an 8 by 8 block of elements each consisting of 4 by 4 ( = 16) lattice sites. + + generator = chaste.mesh.PottsMeshGenerator[2](50, 8, 4, 50, 8, 4) + mesh = generator.GetMesh() + + ## Having created a mesh, we now create some cells. To do this, we the `CellsGenerator` helper class, + ## as before but this time the third argument is set to make all cells non-proliferative. + + differentiated_type = chaste.cell_based.DifferentiatedCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), differentiated_type) + + ## Before we make a CellPopulation we make a cell label and then assign this label to some randomly chosen cells. + + label = chaste.cell_based.CellLabel() + for eachCell in cells: + if chaste.core.RandomNumberGenerator.Instance().ranf() < 0.5: + eachCell.AddCellProperty(label) + + ## Now we have a mesh and a set of cells to go with it, we can create a `CellPopulation`. + + cell_population = chaste.cell_based.PottsBasedCellPopulation[2](mesh, cells) + + ## In order to visualize labelled cells we need to use the following command. + + cell_population.AddCellWriterCellLabelWriter() + + ## PyChaste can do simple 3D rendering with VTK. We set up a VtkScene so that we can + ## see the population evovle in real time. + + scene = chaste.visualization.VtkScene[2]() + scene.SetCellPopulation(cell_population) + scene.GetCellPopulationActorGenerator().SetShowPottsMeshEdges(True) + # JUPYTER_SHOW_FIRST + scene.Start() # JUPYTER_SHOW + + ## We then pass in the cell population into an `OffLatticeSimulation`, and set the output directory and end time + + simulator = chaste.cell_based.OnLatticeSimulation[2](cell_population) + simulator.SetOutputDirectory("Python/TestCellSorting") + simulator.SetEndTime(20.0) + simulator.SetSamplingTimestepMultiple(10) + + ## We must now create one or more update rules, which determine the Hamiltonian in the Potts simulation. + ## For this test, we use two update rules based upon a volume constraint (`VolumeConstraintPottsUpdateRule`) and + ## differential adhesion between cells (`DifferentialAdhesionPottsUpdateRule`), set appropriate parameters, and + ## pass them to the `OnLatticeSimulation`. + + volume_constraint_update_rule = chaste.cell_based.VolumeConstraintPottsUpdateRule[2]() + volume_constraint_update_rule.SetMatureCellTargetVolume(16) + volume_constraint_update_rule.SetDeformationEnergyParameter(0.2) + simulator.AddUpdateRule(volume_constraint_update_rule) + + ## We repeat the process for any other update rules. + + differential_adhesion_update_rule = chaste.cell_based.DifferentialAdhesionPottsUpdateRule[2]() + differential_adhesion_update_rule.SetLabelledCellLabelledCellAdhesionEnergyParameter(0.16) + differential_adhesion_update_rule.SetLabelledCellCellAdhesionEnergyParameter(0.11) + differential_adhesion_update_rule.SetCellCellAdhesionEnergyParameter(0.02) + differential_adhesion_update_rule.SetLabelledCellBoundaryAdhesionEnergyParameter(0.16) + differential_adhesion_update_rule.SetCellBoundaryAdhesionEnergyParameter(0.16) + simulator.AddUpdateRule(differential_adhesion_update_rule) + + ## Set up plotting + + scene_modifier = chaste.cell_based.VtkSceneModifier[2]() + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(1000) + simulator.AddSimulationModifier(scene_modifier) + + ## To run the simulation, we call `Solve()`. + + scene.Start() + simulator.Solve() + + # JUPYTER_TEARDOWN + + +if __name__ == "__main__": + unittest.main(verbosity=2) + +# endif END_WIKI diff --git a/pychaste/test/tutorial/TestPyImmersedBoundaryTutorial.py b/pychaste/test/tutorial/TestPyImmersedBoundaryTutorial.py new file mode 100644 index 0000000000..180a6218f5 --- /dev/null +++ b/pychaste/test/tutorial/TestPyImmersedBoundaryTutorial.py @@ -0,0 +1,457 @@ +"""Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +# ifndef +# define TRIGGER_WIKI + +## ## Introduction +## This tutorial is a demonstration of the immersed boundary method, a technique +## for simulating fluid-structure interactions. We can use the immersed boundary +## method to simulate a cell as a structure with its outer **boundary immersed** +## in a fluid. There is a two-way coupling between the fluid and the structure: +## the flow of the fluid exerts a force on the structure, and the structure +## influences the flow of the fluid. +## +## In this tutorial, we demonstrate: +## 1. Building single-cell immersed boundary capable simulations. +## 2. Building multi-cellular immersed boundary simulations. +## 3. Adding and manipulating immersed boundary fluid sources. +## +## ## Imports + +import unittest + +import chaste + +from chaste.cell_based import ( + AbstractCellBasedTestSuite, + CellsGenerator, + DifferentiatedCellProliferativeType, + ForwardEulerNumericalMethod, + ImmersedBoundaryCellPopulation, + ImmersedBoundaryLinearInteractionForce, + ImmersedBoundaryLinearMembraneForce, + ImmersedBoundarySimulationModifier, + OffLatticeSimulation, +) + +from chaste.mesh import ( + FluidSource, + ImmersedBoundaryPalisadeMeshGenerator, +) + +from chaste.visualization import ( + JupyterNotebookManager, + JupyterSceneModifier, + VtkScene, +) + +class TestPyImmersedBoundaryTutorial(AbstractCellBasedTestSuite): + + ## ### 1. Simple Immersed Boundary Simulations + ## We begin by exploring simulations containing a single cell. This will + ## familiarise you with how to generate immersed boundary cells, the steps + ## involved in setting up an immersed boundary simulation, and the options + ## available for controlling how the cells are generated and behave. + ## + + ## Immersed boundary simulations operate over a square domain, with `x` and `y` + ## coordinates lying in the range `0` to `1`. The domain wraps on both axes - + ## this means that if a cell moves off the right hand edge of the domain, + ## the segment will appear on the left hand side. This is not purely visual; + ## forces are also transmitted across these boundaries. + ## + + ## **Tip** Make sure all your coordinates are between `0` and `1`. + + def test_simple_immersed_boundary_simulation(self): + + ## Setup the simulation environment in the notebook + + # JUPYTER_SETUP + + ## Next, we define the necessary geometry by generating a mesh to + ## contain a single cell. + + gen = ImmersedBoundaryPalisadeMeshGenerator(1, 128, 0.1, 2.0, 0.0, False) + mesh = gen.GetMesh() + + ## The first line of code defines an `ImmersedBoundaryPalisadeMeshGenerator` + ## called `gen`. The 3rd parameter controls the exponent of the superellipse(`0.1`) + ## and the 4th parameter controls the aspect ratio of the cell(`2.0`). You can + ## experiment with modifying these to change the initial shape of the cell. + ## + + ## The second line of code instructs the mesh generator to generate a mesh. + ## Checking the type of mesh with `type(mesh)` will show it as + ## `ImmersedBoundaryMesh_2_2`. The `_2_2` suffix denotes that we are using + ## a 2-dimensional space, and 2-dimensional elements to define the mesh. + ## + + ## We now set the fluid grid resolution. The following code specifies + ## that we are using a 64x64 grid to simulate our fluid over. + + mesh.SetNumGridPtsXAndY(64) + + ## Next, we generate the cells. We specify a cell type and cell cycle model. + ## These can be changed to modify the life cycle of the cells. The + ## cell generator then constructs the necessary information for each + ## of the elements in the mesh. + + cell_type = DifferentiatedCellProliferativeType() + cell_generator = CellsGenerator["UniformCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), cell_type) + + ## Finally, we construct the cell population. We then specify whether the + ## population has active fluid sources or not. For now, we are not + ## using any fluid sources, so we set this to `False` + + cell_population = ImmersedBoundaryCellPopulation[2](mesh, cells) + cell_population.SetIfPopulationHasActiveSources(False) + + ## We can make a quick visualization of the cell population + + scene = VtkScene[2]() + scene.SetCellPopulation(cell_population) + nb_manager = JupyterNotebookManager() + nb_manager.vtk_show(scene, height=300) + + ## Next, we create an `OffLatticeSimulation` simulator to control the + ## simulation. Although the fluid is simulated on a lattice (grid), + ## the nodes/cells are not bound to a lattice. + + simulator = OffLatticeSimulation[2, 2](cell_population) + simulator.SetNumericalMethod(ForwardEulerNumericalMethod[2, 2]()) + simulator.GetNumericalMethod().SetUseUpdateNodeLocation(True) + + ## As we have an off-lattice simulation, we need a way to model the + ## fluid. This is handled by the `ImmersedBoundarySimulationModifier`. + ## Modifiers in Chaste are classes that can be attached to simulations + ## to perform some additional custom functionality each timestep. + ## In this case, the modifier is responsible for solving the + ## Navier-Stokes equations and propagating forces between the nodes and + ## the fluid. + + ib_modifier = ImmersedBoundarySimulationModifier[2]() + simulator.AddSimulationModifier(ib_modifier) + + ## We must also provide the modifier with a force model to govern + ## interactions between the nodes forming the cell membrane. + ## Note that these forces only act between nodes in the same cell; + ## they do not control interactions between cells. + + membrane_force = ImmersedBoundaryLinearMembraneForce[2]() + membrane_force.SetElementSpringConst(1.0 * 1e7) + ib_modifier.AddImmersedBoundaryForce(membrane_force) + + ## The `ImmersedBoundaryLinearMembraneForce` models forces between + ## membrane nodes using linear springs i.e, the force applied is + ## proportional to the deviation of the distance between nodes + ## from a rest length. The spring constant(`1.0 * 1e7`) defines how + ## stiff the cell boundary is. + ## + + ## **Practice** Experiment with adjusting the spring constant to + ## change the force behaviour between nodes of the cell boundary. + ## + + ## Next, we set the simulation properties + + dt = 0.05 + simulator.SetOutputDirectory("Python/TestImmersedBoundary_1") + simulator.SetDt(dt) + simulator.SetSamplingTimestepMultiple(4) + simulator.SetEndTime(1000 * dt) + + ## We can add a modifier to visualize the cell population while the + ## simulation is in progress + + scene_modifier = JupyterSceneModifier[2](nb_manager) + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(1000) + simulator.AddSimulationModifier(scene_modifier) + + ## Finally, to run the simulation we call the `Solve()` method. + + simulator.Solve() + + ## Reset the simulation environment in the notebook + + # JUPYTER_TEARDOWN + + ## ### 2. Adding More Cells + + def test_multicell_immersed_boundary_simulation(self): + + ## #### Multiple Cells + + ## Setup the simulation environment in the notebook + + # JUPYTER_SETUP + + ## We can use the mesh generator to generate multiple cells. The first + ## parameter of the mesh generator constructor controls the number of + ## cells. + ## + + ## **Practice** Try increasing the number of cells by adjusting the + ## parameter value. A sensible range for this tutorial is 4-10 cells. + + gen = ImmersedBoundaryPalisadeMeshGenerator(5, 128, 0.1, 2.0, 0.0, False) + + ## #### Laminas + ## In addition to the cells we have seen so far, we can introduce + ## laminas to the simulation. Laminas are surfaces with reduced + ## dimensionality. For 3D elements, a lamina is a 2D surface. For the + ## 2D elements we are currently working with, laminas are lines. + ## Changing the last parameter of the mesh generator constructor from `False` + ## to `True` will generate a basal lamina spanning the palisade cells. + ## Laminas can also interact with the fluid field, and can be made + ## "leaky" to allow some flow across their boundary. This can be used + ## to model a permeable boundary. + ## + + ## **Practice** Try changing the 6th constructor parameter to create a lamina. + ## + + ## #### Cell Variations + ## Apart from using the 3rd and 4th constructor parameters to modify + ## the cell shapes, we can also introduce variation between cells by + ## modifying the 5th parameter. + ## + + ## **Practice** Try adjusting the 3rd and 4th constructor parameters to + ## introduce cell variations. + ## + + ## Next, we generate the mesh and set the fluid grid resolution + + mesh = gen.GetMesh() + mesh.SetNumGridPtsXAndY(64) + + ## Below, we generate the cells + + cell_type = DifferentiatedCellProliferativeType() + cell_generator = CellsGenerator["UniformCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), cell_type) + + ## Then we set up the cell population with no active fluid sources + + cell_population = ImmersedBoundaryCellPopulation[2](mesh, cells) + cell_population.SetIfPopulationHasActiveSources(False) + + ## We can visualize the cell population below + + scene = VtkScene[2]() + scene.SetCellPopulation(cell_population) + nb_manager = JupyterNotebookManager() + nb_manager.vtk_show(scene, height=300) + + ## Now we create a simulator to manage the simulation + + simulator = OffLatticeSimulation[2, 2](cell_population) + simulator.SetNumericalMethod(ForwardEulerNumericalMethod[2, 2]()) + simulator.GetNumericalMethod().SetUseUpdateNodeLocation(True) + + ## We add an immersed boundary simulation modifier to the simulator + + ib_modifier = ImmersedBoundarySimulationModifier[2]() + simulator.AddSimulationModifier(ib_modifier) + + ## We then add a force law to the simulation modifier to model the + ## behaviour of the cell membrane + + membrane_force = ImmersedBoundaryLinearMembraneForce[2]() + membrane_force.SetElementSpringConst(1.0 * 1e7) + ib_modifier.AddImmersedBoundaryForce(membrane_force) + + ## #### Inter-cellular Interactions + ## So far, we have encountered forces that act to maintain the shape + ## of the cell membrane. We can also introduce an inter-cellular + ## force law using `ImmersedBoundaryLinearInteractionForce`. + ## This has a `SetSpringConst` method instead of a `SetElementSpringConst` + ## method. It also has a `SetRestLength` method that we can use to + ## modify the rest length. + + interaction_force = ImmersedBoundaryLinearInteractionForce[2]() + interaction_force.SetSpringConst(1.0 * 1e6) + ib_modifier.AddImmersedBoundaryForce(interaction_force) + + ## Next, we set the simulation properties + + dt = 0.05 + simulator.SetOutputDirectory("Python/TestImmersedBoundary_2") + simulator.SetDt(dt) + simulator.SetSamplingTimestepMultiple(4) + simulator.SetEndTime(1000 * dt) + + ## Finally, we run the simulation + + simulator.Solve() + + ## We can visualize the end state of the cell population + + nb_manager.vtk_show(scene, height=300) + + ## Reset the simulation environment in the notebook + + # JUPYTER_TEARDOWN + + ## ### 3. Adding Fluid Sources + ## Now that we are familiar with how to generate the cells, we will + ## introduce fluid sources. + + def test_fluid_source_immersed_boundary_simulation(self): + + ## #### Adding a Fluid Source + + ## Setup the simulation environment in the notebook + + # JUPYTER_SETUP + + ## We begin by constructing a fluid source object: + + source = FluidSource[2](0, 0.5, 0.7) + + ## This constructs a `FluidSource` object in 2 dimensions. The first + ## parameter supplies the index of the fluid source. Each source we + ## create must have a unique index. The next two parameters are the + ## `x` and `y` coordinates of the source. Fluid sources in Chaste are + ## point-like, that is to say they do not have any area/volume. + ## + ## Having created the fluid source, we set its strength: + + source.SetStrength(0.012) + + ## Next, we create the mesh + + gen = ImmersedBoundaryPalisadeMeshGenerator(5, 128, 0.1, 2.0, 0.0, False) + mesh = gen.GetMesh() + mesh.SetNumGridPtsXAndY(64) + + ## We must associate the source with an element in the simulation + ## so that the simulation is aware of the source. + + mesh.GetElement(0).SetFluidSource(source) + + ## We now generate the cells + + cell_type = DifferentiatedCellProliferativeType() + cell_generator = CellsGenerator["UniformCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), cell_type) + + ## Then we set up the cell population + + cell_population = ImmersedBoundaryCellPopulation[2](mesh, cells) + + ## Finally, we must tell the cell population that fluid sources are present. + + cell_population.SetIfPopulationHasActiveSources(True) + + ## #### Varying the Source Location and Strength + ## **Practice** You can experiment with the source location. Try moving it + ## closer to and further away from the cells. + ## + + ## **Practice** Try modifying the source strength to see what impact this + ## has on the cell shapes. + ## + + ## Below, we visualize the cell population + + scene = VtkScene[2]() + scene.SetCellPopulation(cell_population) + nb_manager = JupyterNotebookManager() + nb_manager.vtk_show(scene, height=300) + + ## Create a simulator to manage the simulation + + simulator = OffLatticeSimulation[2, 2](cell_population) + simulator.SetNumericalMethod(ForwardEulerNumericalMethod[2, 2]()) + simulator.GetNumericalMethod().SetUseUpdateNodeLocation(True) + + ## Add an immersed boundary simulation modifier + + ib_modifier = ImmersedBoundarySimulationModifier[2]() + simulator.AddSimulationModifier(ib_modifier) + + ## #### Fluid-Cell Interaction + ## **Practice** Try modifying the spring constant of the + ## `ImmersedBoundaryLinearMembraneForce` to see how this changes the + ## effect of the fluid source on the cells. + + membrane_force = ImmersedBoundaryLinearMembraneForce[2]() + membrane_force.SetElementSpringConst(1.0 * 1e7) + ib_modifier.AddImmersedBoundaryForce(membrane_force) + + ## Add an inter-cellular force law + + interaction_force = ImmersedBoundaryLinearInteractionForce[2]() + interaction_force.SetSpringConst(1.0 * 1e6) + ib_modifier.AddImmersedBoundaryForce(interaction_force) + + ## #### Adding More Sources + ## **Practice** Try adding a second fluid source. You will need to + ## use a unique index, and attach it to a different element as + ## each element can only manage a single fluid source. + ## + + ## Next, we set the simulation properties + + dt = 0.05 + simulator.SetOutputDirectory("Python/TestImmersedBoundary_3") + simulator.SetDt(dt) + simulator.SetSamplingTimestepMultiple(4) + simulator.SetEndTime(300 * dt) + + ## Finally, we run the simulation + + simulator.Solve() + + ## Then we visualize the end state + + nb_manager.vtk_show(scene, height=300) + + ## Reset the simulation environment in the notebook + # JUPYTER_TEARDOWN + + ## #### Further Exercises + ## * Try integrating a different cell cycle model to introduce cell + ## division. See how the presence of a fluid source impacts the + ## structure that is formed. + ## * Use one of the cell writers to collect some statistics + + +if __name__ == "__main__": + unittest.main(verbosity=2) + +# endif END_WIKI diff --git a/pychaste/test/tutorial/TestPyMeshBasedCellSimulationsTutorial.py b/pychaste/test/tutorial/TestPyMeshBasedCellSimulationsTutorial.py new file mode 100644 index 0000000000..edbfdb5a12 --- /dev/null +++ b/pychaste/test/tutorial/TestPyMeshBasedCellSimulationsTutorial.py @@ -0,0 +1,227 @@ +"""Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +# ifndef +# define TRIGGER_WIKI + +## ## Introduction +## In this tutorial we show how Chaste can be used to create, run and visualize mesh-based simulations. +## Full details of the mathematical model can be found in van Leeuwen et al. (2009) [doi:10.1111/j.1365-2184.2009.00627.x]. +## +## ## The Test + +import unittest # Python testing framework + +import matplotlib.pyplot as plt # Plotting +import numpy as np # Matrix tools + +import chaste # The PyChaste module +import chaste.cell_based # Contains cell populations +import chaste.mesh # Contains meshes +import chaste.visualization # Visualization tools + +from chaste.cell_based import AbstractCellBasedTestSuite + + +class TestPyMeshBasedCellSimulationsTutorial(AbstractCellBasedTestSuite): + + ## ### Test 1 - a basic mesh-based simulation + ## In the first test, we run a simple mesh-based simulation, + ## in which we create a monolayer of cells, using a mutable mesh. Each cell is assigned a stochastic cell-cycle model. + + def test_monolayer(self): + + # JUPYTER_SETUP + + ## Next, we generate a mutable mesh. To create a `MutableMesh`, we can use the `HoneycombMeshGenerator`. + ## This generates a honeycomb-shaped mesh, in which all nodes are equidistant. Here the first and second arguments define the size of the mesh - + ## we have chosen a mesh that is 4 nodes (i.e. cells) wide, and 4 nodes high. + + chaste.core.OutputFileHandler("Python/TestMeshBasedCellSimulationsTutorial") + generator = chaste.mesh.HoneycombMeshGenerator(4, 4) + mesh = generator.GetMesh() + + ## Having created a mesh, we now create some cells. To do this, we use the `CellsGenerator` helper class, + ## which is specialized by the type of cell cycle model required (here `UniformCellCycleModel`) and the dimension. + ## For a list of possible cell cycle models see subclasses of `AbstractCellCycleModel`. + ## Note that some of these models will require information on the surrounding medium such as Oxygen concentration to work, + ## see specific class documentation for details. We create an empty vector of cells and pass this into the method along with the mesh. + ## The second argument represents the size of that the list of cells should become - one cell for each node, + ## the third argument specifies the proliferative type of the cell. + + transit_type = chaste.cell_based.TransitCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumNodes(), transit_type) + + ## Now we have a mesh and a set of cells to go with it, we can create a `CellPopulation`. + ## In general, this class associates a collection of cells with a mesh. For this test, because we have a `MutableMesh`, + ## we use a particular type of cell population called a `MeshBasedCellPopulation`. + + cell_population = chaste.cell_based.MeshBasedCellPopulation[2, 2](mesh, cells) + + ## To view the results of this and the next test in Paraview it is necessary to explicitly + ## generate the required .vtu files. + + cell_population.AddPopulationWriterVoronoiDataWriter() + + ## We can set up a `VtkScene` to do a quick visualization of the population before running the analysis. + + scene = chaste.visualization.VtkScene[2]() + scene.SetCellPopulation(cell_population) + # JUPYTER_SHOW_FIRST + scene.Start() # JUPYTER_SHOW + + ## We then pass in the cell population into an `OffLatticeSimulation`, and set the output directory and end time. + + simulator = chaste.cell_based.OffLatticeSimulation[2, 2](cell_population) + simulator.SetOutputDirectory("Python/TestMeshBasedCellSimulationsTutorial") + simulator.SetEndTime(10.0) + + ## For longer simulations, we may not want to output the results every time step. In this case we can use the following method, + ## to print results every 12 time steps instead. As the default time step used by the simulator is 30 seconds, + ## this method will cause the simulator to print results every 6 minutes (or 0.1 hours). + + simulator.SetSamplingTimestepMultiple(12) + + ## We must now create one or more force laws, which determine the mechanics of the centres of each cell in a cell population. + ## For this test, we use one force law, based on the spring based model, and pass it to the `OffLatticeSimulation`. + ## For a list of possible forces see subclasses of `AbstractForce`. Note that some of these forces are not compatible with mesh-based simulations, + ## see the specific class documentation for details. If you try to use an incompatible class then you will receive a warning. + + force = chaste.cell_based.GeneralisedLinearSpringForce[2, 2]() + simulator.AddForce(force) + + ## Save snapshot images of the population during the simulation + + scene_modifier = chaste.cell_based.VtkSceneModifier[2]() + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(100) + simulator.AddSimulationModifier(scene_modifier) + + ## To run the simulation, we call `Solve()`. We can again do a quick rendering of the population at the end of the simulation + + scene.Start() + simulator.Solve() + scene.End() + + # JUPYTER_TEARDOWN + + ## Full results can be visualized in Paraview from the `file_handler.GetOutputDirectoryFullPath()` directory. + + ## ### Test 2 - a basic mesh-based simulation with ghost nodes + ## In the second test, we run a simple mesh-based simulation with ghost nodes, in which we create a monolayer of cells, using a mutable mesh. + ## Each cell is assigned a stochastic cell-cycle model. + + def test_monolayer_with_ghost_nodes(self): + + # JUPYTER_SETUP + + ## We start by generating a mutable mesh. To create a `MutableMesh`, we can use the `HoneycombMeshGenerator` as before. + ## Here the first and second arguments define the size of the mesh - we have chosen a mesh that is 2 nodes (i.e. cells) wide, + ## and 2 nodes high. The third argument specifies the number of layers of ghost nodes to make. + + chaste.core.OutputFileHandler("Python/TestMeshBasedCellPopulationWithGhostNodes") + generator = chaste.mesh.HoneycombMeshGenerator(5, 5, 2) + mesh = generator.GetMesh() + + ## We only want to create cells to attach to real nodes, so we use the method `GetCellLocationIndices` to get the + ## indices of the real nodes in the mesh. This will be passed in to the cell population later on. + + locs = generator.GetCellLocationIndices() + + ## Having created a mesh, we now create some cells. To do this, we use the `CellsGenerator` helper class again. + ## This time the second argument is different and is the number of real nodes in the mesh. + ## As before all cells have `TransitCellProliferativeType`. + + transit_type = chaste.cell_based.TransitCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(len(locs), transit_type) + + ## Now we have a mesh and a set of cells to go with it, we can create a `CellPopulation`. + ## In general, this class associates a collection of cells with a set of elements or a mesh. + ## For this test, because we have a `MutableMesh`, and ghost nodes we use a particular type of cell population called + ## a `MeshBasedCellPopulationWithGhostNodes`. The third argument of the constructor takes a vector of the indices of the real nodes + ## and should be the same length as the vector of cell pointers. + + cell_population = chaste.cell_based.MeshBasedCellPopulationWithGhostNodes[2](mesh, cells, locs) + + ## Again Paraview output is explicitly requested. + + cell_population.AddPopulationWriterVoronoiDataWriter() + + ## We can set up a `VtkScene` to do a quick visualization of the population before running the analysis. + + scene = chaste.visualization.VtkScene[2]() + scene.SetCellPopulation(cell_population) + scene.GetCellPopulationActorGenerator().SetShowVoronoiMeshEdges(True) + # JUPYTER_SHOW + + ## We then pass in the cell population into an `OffLatticeSimulation`, and set the output directory, output multiple and end time. + + simulator = chaste.cell_based.OffLatticeSimulation[2, 2](cell_population) + simulator.SetOutputDirectory("Python/TestMeshBasedCellPopulationWithGhostNodes") + simulator.SetEndTime(10.0) + simulator.SetSamplingTimestepMultiple(12) + + ## Save snapshot images of the population during the simulation + + scene_modifier = chaste.cell_based.VtkSceneModifier[2]() + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(300) + simulator.AddSimulationModifier(scene_modifier) + + ## Again we create a force law, and pass it to the `OffLatticeSimulation`. + ## This force law ensures that ghost nodes don't exert forces on real nodes but real nodes exert forces on ghost nodes. + + force = chaste.cell_based.GeneralisedLinearSpringForce[2, 2]() + simulator.AddForce(force) + + ## To run the simulation, we call `Solve()`. + + scene.Start() + simulator.Solve() + + ## The next two lines are for test purposes only and are not part of this tutorial. + ## If different simulation input parameters are being explored the lines should be removed. + + self.assertEqual(cell_population.GetNumRealCells(), 48) + self.assertAlmostEqual(chaste.cell_based.SimulationTime.Instance().GetTime(), 10.0, 6) + + # JUPYTER_TEARDOWN + + ## Full results can be visualized in Paraview from the `file_handler.GetOutputDirectoryFullPath()` directory. + + +if __name__ == "__main__": + unittest.main(verbosity=2) + +# endif END_WIKI diff --git a/pychaste/test/tutorial/TestPyNodeBasedCellSimulationsTutorial.py b/pychaste/test/tutorial/TestPyNodeBasedCellSimulationsTutorial.py new file mode 100644 index 0000000000..cd4f665fad --- /dev/null +++ b/pychaste/test/tutorial/TestPyNodeBasedCellSimulationsTutorial.py @@ -0,0 +1,298 @@ +"""Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +# ifndef +# define TRIGGER_WIKI + +## ## Introduction +## In this tutorial we show how Chaste can be used to create, run and visualize node-based simulations. Full details of the mechanical model can be found in Pathamathan et +## al "A computational study of discrete mechanical tissue models", Physical Biology. Vol. 6. No. 3. 2009.. DOI (10.1088/1478-3975/6/3/036001). +## +## ## The Test + +import unittest # Python testing framework + +import numpy as np # Matrix tools + +import chaste # The PyChaste module +import chaste.cell_based # Contains cell populations +import chaste.mesh # Contains meshes +import chaste.visualization # Visualization tools + +from chaste.cell_based import AbstractCellBasedTestSuite + + +class TestPyNodeBasedCellSimulationsTutorial(AbstractCellBasedTestSuite): + ## ### Test 1 - A basic node-based simulation + ## In the first test, we run a simple node-based simulation, in which we create a monolayer of cells, + ## using a nodes only mesh. Each cell is assigned a uniform cell-cycle model. + + def test_monolayer(self): + + # JUPYTER_SETUP + + ## The first thing we do is generate a nodes only mesh. To do this we first create a `MutableMesh` to use as a generating mesh. + ## To do this we can use the `HoneycombMeshGenerator`. This generates a honeycomb-shaped mesh, in which all nodes are equidistant. + ## Here the first and second arguments define the size of the mesh - we have chosen a mesh that is 2 nodes (i.e. cells) wide, and 2 nodes high. + + chaste.core.OutputFileHandler("Python/TestNodeBasedCellSimulationsTutorial") + generator = chaste.mesh.HoneycombMeshGenerator(2, 2) + generating_mesh = generator.GetMesh() + + ## Once we have a MutableMesh we can generate a NodesOnlyMesh from it using the following commands. + ## Note you can also generate the NodesOnlyMesh from a collection of nodes. + + mesh = chaste.mesh.NodesOnlyMesh[2]() + + ## To run node-based simulations you need to define a cut off length (second argument in `ConstructNodesWithoutMesh`), + ## which defines the connectivity of the nodes by defining a radius of interaction. + + mesh.ConstructNodesWithoutMesh(generating_mesh, 1.5) + + ## Having created a mesh, we now create a (wrapped) vector of CellPtrs. To do this, we use the `CellsGenerator` helper class, + ## which is specialized for the type of cell model required (here `UniformCellCycleModel`) and the dimension. + ## We create an empty vector of cells and pass this into the method along with the mesh. + ## The second argument represents the size of that the vector cells should become - one cell for each node, + ## the third argument specifies the proliferative type of the cell. + + transit_type = chaste.cell_based.TransitCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumNodes(), transit_type) + + ## Now we have a mesh and a set of cells to go with it, we can create a `CellPopulation`. + ## In general, this class associates a collection of cells with a mesh. For this test, + ## because we have a `NodesOnlyMesh`, we use a particular type of cell population called a `NodeBasedCellPopulation`. + + cell_population = chaste.cell_based.NodeBasedCellPopulation[2](mesh, cells) + + ## We can set up a `VtkScene` to do a quick visualization of the population before running the analysis. + + scene = chaste.visualization.VtkScene[2]() + scene.SetCellPopulation(cell_population) + # JUPYTER_SHOW_FIRST + scene.Start() # JUPYTER_SHOW + + ## We then pass in the cell population into an `OffLatticeSimulation`, and set the output directory, output multiple and end time + + simulator = chaste.cell_based.OffLatticeSimulation[2, 2](cell_population) + simulator.SetOutputDirectory("Python/TestNodeBasedCellSimulationsTutorial") + simulator.SetSamplingTimestepMultiple(100) + simulator.SetEndTime(10.0) + + ## We now pass a force law to the simulation. + + force = chaste.cell_based.GeneralisedLinearSpringForce[2, 2]() + simulator.AddForce(force) + + ## Save snapshot images of the population during the simulation + + scene_modifier = chaste.cell_based.VtkSceneModifier[2]() + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(100) + simulator.AddSimulationModifier(scene_modifier) + + ## To run the simulation, we call `Solve()`. We can again do a quick rendering of the population at the end of the simulation + + scene.Start() + simulator.Solve() + scene.End() + + ## The next two lines are for test purposes only and are not part of this tutorial. + ## If different simulation input parameters are being explored the lines should be removed. + + self.assertEqual(cell_population.GetNumRealCells(), 8) + self.assertAlmostEqual(chaste.cell_based.SimulationTime.Instance().GetTime(), 10.0, 6) + + # JUPYTER_TEARDOWN + + ## ### Test 2 - a basic node-based simulation in 3D + ## In the second test we run a simple node-based simulation in 3D. This is very similar to the 2D test with the dimension changed from 2 to 3 and + ## instead of using a mesh generator we generate the nodes directly. + + def test_spheroid(self): + + # JUPYTER_SETUP + + ## First, we generate a nodes only mesh. This time we specify the nodes manually by first creating a vector of nodes + + chaste.core.OutputFileHandler( + "Python/TestNodeBasedCellSimulationsSpheroidTutorial" + ) + nodes = [] + nodes.append(chaste.mesh.Node[3](0, False, 0.5, 0.0, 0.0)) + nodes.append(chaste.mesh.Node[3](1, False, -0.5, 0.0, 0.0)) + nodes.append(chaste.mesh.Node[3](2, False, 0.0, 0.5, 0.0)) + nodes.append(chaste.mesh.Node[3](3, False, 0.0, -0.5, 0.0)) + + ## Finally a NodesOnlyMesh is created and the vector of nodes is passed to the ConstructNodesWithoutMesh method. + + mesh = chaste.mesh.NodesOnlyMesh[3]() + + ## To run node-based simulations you need to define a cut off length (second argument in ConstructNodesWithoutMesh), + ## which defines the connectivity of the nodes by defining a radius of interaction. + + mesh.ConstructNodesWithoutMesh(nodes, 1.5) + + ## Having created a mesh, we now create a std::vector of CellPtrs. + ## As before, we do this with the CellsGenerator helper class (this time with dimension 3). + + transit_type = chaste.cell_based.TransitCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformCellCycleModel", 3]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumNodes(), transit_type) + + ## Now we have a mesh and a set of cells to go with it, we can create a `CellPopulation`. + ## In general, this class associates a collection of cells with a mesh. For this test, + ## because we have a `NodesOnlyMesh`, we use a particular type of cell population called a `NodeBasedCellPopulation`. + + cell_population = chaste.cell_based.NodeBasedCellPopulation[3](mesh, cells) + + ## We can set up a `VtkScene` to do a quick visualization of the population before running the analysis. + + scene = chaste.visualization.VtkScene[3]() + scene.SetCellPopulation(cell_population) + scene.Start() # JUPYTER_SHOW + + ## We then pass in the cell population into an `OffLatticeSimulation`, and set the output directory, output multiple and end time + + simulator = chaste.cell_based.OffLatticeSimulation[3, 3](cell_population) + simulator.SetOutputDirectory("Python/TestNodeBasedCellSimulationsSpheroidTutorial") + simulator.SetSamplingTimestepMultiple(12) + simulator.SetEndTime(10.0) + + ## We now pass a force law to the simulation. + + force = chaste.cell_based.GeneralisedLinearSpringForce[3, 3]() + simulator.AddForce(force) + + ## Save snapshot images of the population during the simulation + + scene_modifier = chaste.cell_based.VtkSceneModifier[3]() + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(100) + simulator.AddSimulationModifier(scene_modifier) + + ## To run the simulation, we call `Solve()`. We can again do a quick rendering of the population at the end of the simulation + + scene.Start() + simulator.Solve() + scene.End() + + ## The next two lines are for test purposes only and are not part of this tutorial. + ## If different simulation input parameters are being explored the lines should be removed. + + self.assertEqual(cell_population.GetNumRealCells(), 8) + self.assertAlmostEqual(chaste.cell_based.SimulationTime.Instance().GetTime(), 10.0, 6) + + # JUPYTER_TEARDOWN + + ## ### Test 3 - a node-based simulation on a restricted geometry + ## In the second test we run a simple node-based simulation in 3D. This is very similar to the 2D test with the dimension changed from 2 to 3 and + ## instead of using a mesh generator we generate the nodes directly. + + def test_spheroid_on_sphere(self): + + # JUPYTER_SETUP + + ## In the third test we run a node-based simulation restricted to the surface of a sphere. + + chaste.core.OutputFileHandler("Python/TestNodeBasedCellSimulationsRestrictedSpheroidTutorial") + nodes = [] + nodes.append(chaste.mesh.Node[3](0, False, 0.5, 0.0, 0.0)) + nodes.append(chaste.mesh.Node[3](1, False, -0.5, 0.0, 0.0)) + nodes.append(chaste.mesh.Node[3](2, False, 0.0, 0.5, 0.0)) + nodes.append(chaste.mesh.Node[3](3, False, 0.0, -0.5, 0.0)) + mesh = chaste.mesh.NodesOnlyMesh[3]() + + ## To run node-based simulations you need to define a cut off length (second argument in ConstructNodesWithoutMesh), + ## which defines the connectivity of the nodes by defining a radius of interaction. + + mesh.ConstructNodesWithoutMesh(nodes, 1.5) + + transit_type = chaste.cell_based.TransitCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformCellCycleModel", 3]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumNodes(), transit_type) + cell_population = chaste.cell_based.NodeBasedCellPopulation[3](mesh, cells) + + ## We can set up a `VtkScene` to do a quick visualization of the population before running the analysis. + + scene = chaste.visualization.VtkScene[3]() + scene.SetCellPopulation(cell_population) + scene.Start() # JUPYTER_SHOW + + simulator = chaste.cell_based.OffLatticeSimulation[3, 3](cell_population) + simulator.SetOutputDirectory("Python/TestNodeBasedCellSimulationsRestrictedSpheroidTutorial") + simulator.SetSamplingTimestepMultiple(12) + simulator.SetEndTime(10.0) + + ## We now pass a force law to the simulation. + + force = chaste.cell_based.GeneralisedLinearSpringForce[3, 3]() + simulator.AddForce(force) + + ## This time we create a CellPopulationBoundaryCondition and pass this to the OffLatticeSimulation. + ## Here we use a SphereGeometryBoundaryCondition which restricts cells to lie on a sphere (in 3D) or circle (in 2D). + ## For a list of possible boundary conditions see subclasses of AbstractCellPopulationBoundaryCondition. + ## Note that some of these boundary conditions are not compatible with node-based simulations see the specific class documentation + ## for details, if you try to use an incompatible class then you will receive a warning. + ## First we set the centre (0,0,1) and radius of the sphere (1). + + centre = np.array([0.0, 0.0, 1.0]) + radius = 5.0 + point2 = chaste.mesh.ChastePoint[3](centre) + boundary_condition = chaste.cell_based.SphereGeometryBoundaryCondition[3](cell_population, point2.rGetLocation(), radius) + simulator.AddCellPopulationBoundaryCondition(boundary_condition) + + ## Save snapshot images of the population during the simulation + scene_modifier = chaste.cell_based.VtkSceneModifier[3]() + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(100) + simulator.AddSimulationModifier(scene_modifier) + + ## To run the simulation, we call `Solve()`. We can again do a quick rendering of the population at the end of the simulation + + scene.Start() + simulator.Solve() + scene.End() + + ## The next two lines are for test purposes only and are not part of this tutorial. + ## If different simulation input parameters are being explored the lines should be removed. + + self.assertEqual(cell_population.GetNumRealCells(), 8) + self.assertAlmostEqual(chaste.cell_based.SimulationTime.Instance().GetTime(), 10.0, 6) + + # JUPYTER_TEARDOWN + + +if __name__ == "__main__": + unittest.main(verbosity=2) + +# endif END_WIKI diff --git a/pychaste/test/tutorial/TestPyPottsBasedCellSimulationsTutorial.py b/pychaste/test/tutorial/TestPyPottsBasedCellSimulationsTutorial.py new file mode 100644 index 0000000000..4dac3cf958 --- /dev/null +++ b/pychaste/test/tutorial/TestPyPottsBasedCellSimulationsTutorial.py @@ -0,0 +1,316 @@ +"""Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +# ifndef +# define TRIGGER_WIKI + +## ## Introduction +## In this tutorial we show how Chaste can be used to create, run and visualize Potts-based simulations. +## Full details of the mathematical model can be found in Graner, F. and Glazier, J. A. (1992). +## +## ## The Test + +import unittest # Python testing framework + +import chaste # The PyChaste module +import chaste.cell_based # Contains cell populations +import chaste.mesh # Contains meshes +import chaste.visualization # Visualization tools + +from chaste.cell_based import AbstractCellBasedTestSuite + +class TestPyPottsBasedCellSimulationsTutorial(AbstractCellBasedTestSuite): + ## ### Test 1 - A basic node-based simulation + ## In the first test, we run a simple Potts-based simulation, in which we create a monolayer of cells, using a Potts mesh. + ## Each cell is assigned a stochastic cell-cycle model. + + def test_monolayer(self): + + # JUPYTER_SETUP + + ## First, we generate a Potts mesh. To create a PottsMesh, we can use the PottsMeshGenerator. + ## This generates a regular square-shaped mesh, in which all elements are the same size. + ## Here the first three arguments specify the domain width; the number of elements across; and the width of elements. + ## The second set of three arguments specify the domain height; the number of elements up; and the height of individual elements. + ## We have chosen a 2 by 2 block of elements, each consisting of 4 by 4 ( = 16) lattice sites. + + chaste.core.OutputFileHandler("Python/TestPottsBasedCellSimulationsTutorial") + generator = chaste.mesh.PottsMeshGenerator[2](50, 2, 4, 50, 2, 4) + mesh = generator.GetMesh() + + ## Having created a mesh, we now create a vector of CellPtrs. To do this, we the CellsGenerator helper class, + ## which is templated over the type of cell model required and the dimension. + ## We create an empty vector of cells and pass this into the method along with the mesh. + ## The second argument represents the size of that the vector cells should become - one cell for each element. + ## Third argument makes all cells proliferate. + + cell_generator = chaste.cell_based.CellsGenerator["UniformCellCycleModel", 2]() + cells = cell_generator.GenerateBasic(mesh.GetNumElements()) + + ## Now we have a mesh and a set of cells to go with it, we can create a CellPopulation. + ## In general, this class associates a collection of cells with a mesh. For this test, because we have a PottsMesh, + ## we use a particular type of cell population called a PottsBasedCellPopulation. + + cell_population = chaste.cell_based.PottsBasedCellPopulation[2](mesh, cells) + + ## We can set the "Temperature" to be used in the Potts Simulation using the optional command below. The default value is 0.1. + + cell_population.SetTemperature(0.1) + + ## By default the Potts simulation will make 1 sweep over the whole domain per timestep. + ## To use a different number of sweeps per timestep use the command. + + cell_population.SetNumSweepsPerTimestep(1) + + ## We can set up a `VtkScene` to do a quick visualization of the population before running the analysis. + + scene = chaste.visualization.VtkScene[2]() + scene.SetCellPopulation(cell_population) + scene.GetCellPopulationActorGenerator().SetShowPottsMeshEdges(True) + # JUPYTER_SHOW_FIRST + scene.Start() # JUPYTER_SHOW + + ## We then pass in the cell population into an `OffLatticeSimulation`, and set the output directory and end time + + simulator = chaste.cell_based.OnLatticeSimulation[2](cell_population) + simulator.SetOutputDirectory("Python/TestPottsBasedCellSimulationsTutorial") + simulator.SetEndTime(50.0) + + ## The default timestep is 0.1, but can be changed using the below command. The timestep is used in conjunction with the "Temperature" + ## and number of sweeps per timestep to specify the relationship between cell movement and proliferation. + ## We also set the simulation to only output every 10 steps i.e. once per hour. + + simulator.SetDt(0.1) + simulator.SetSamplingTimestepMultiple(10) + + ## We must now create one or more update rules, which determine the Hamiltonian in the Potts simulation. + ## For this test, we use two update rules based upon a volume constraint (VolumeConstraintPottsUpdateRule) + ## and adhesion between cells (AdhesionPottsUpdateRule) and pass them to the OnLatticeSimulation. + ## For a list of possible update rules see subclasses of AbstractPottsUpdateRule. + + volume_constraint_update_rule = (chaste.cell_based.VolumeConstraintPottsUpdateRule[2]()) + + ## Set an appropriate target volume in number of lattice sites. Here we use the default value of 16 lattice sites. + + volume_constraint_update_rule.SetMatureCellTargetVolume(16) + + ## You can also vary the deformation energy parameter. + ## The larger the parameter the more cells will try to maintain target volume. Here we use the default value of 0.2. + + volume_constraint_update_rule.SetDeformationEnergyParameter(0.2) + + ## Finally we add the update rule to the simulator. + + simulator.AddUpdateRule(volume_constraint_update_rule) + + ## We repeat the process for any other update rules. + + adhesion_update_rule = chaste.cell_based.AdhesionPottsUpdateRule[2]() + simulator.AddUpdateRule(adhesion_update_rule) + + ## Save snapshot images of the population during the simulation + + scene_modifier = chaste.cell_based.VtkSceneModifier[2]() + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(100) + simulator.AddSimulationModifier(scene_modifier) + + ## To run the simulation, we call `Solve()`. We can again do a quick rendering of the population at the end of the simulation + + scene.Start() + simulator.Solve() + + ## The next two lines are for test purposes only and are not part of this tutorial. + ## If different simulation input parameters are being explored the lines should be removed. + + self.assertEqual(cell_population.GetNumRealCells(), 41) + self.assertAlmostEqual(chaste.cell_based.SimulationTime.Instance().GetTime(), 50.0, 6) + + # JUPYTER_TEARDOWN + + ## ### Test 2 - Cell sorting + ## The next test generates a collection of cells, there are two types of cells, labelled ones and non labelled ones, + ## there is differential adhesion between the cell types. For the parameters specified, the cells sort into separate types. + + def test_potts_monolayer_cell_sorting(self): + + # JUPYTER_SETUP + + ## First, we generate a Potts mesh. To create a PottsMesh, we can use the PottsMeshGenerator. + ## This generates a regular square-shaped mesh, in which all elements are the same size. + ## We have chosen an 8 by 8 block of elements each consisting of 4 by 4 ( = 16) lattice sites. + + generator = chaste.mesh.PottsMeshGenerator[2](50, 8, 4, 50, 8, 4) + mesh = generator.GetMesh() + + ## Having created a mesh, we now create a VectorSharedPtrCells. To do this, we the CellsGenerator helper class, + ## as before but this time the third argument is set to make all cells non-proliferative. + + differentiated_type = chaste.cell_based.DifferentiatedCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), differentiated_type) + + ## Before we make a CellPopulation we make a cell label and then assign this label to some randomly chosen cells. + + label = chaste.cell_based.CellLabel() + for eachCell in cells: + if chaste.core.RandomNumberGenerator.Instance().ranf() < 0.5: + eachCell.AddCellProperty(label) + + ## Now we have a mesh and a set of cells to go with it, we can create a CellPopulation. + + cell_population = chaste.cell_based.PottsBasedCellPopulation[2](mesh, cells) + + ## In order to visualize labelled cells we need to use the following command. + + cell_population.AddCellWriterCellLabelWriter() + + ## We then pass in the cell population into an `OffLatticeSimulation`, and set the output directory and end time + + simulator = chaste.cell_based.OnLatticeSimulation[2](cell_population) + simulator.SetOutputDirectory("Python/TestPottsBasedCellSorting") + simulator.SetEndTime(20.0) + simulator.SetSamplingTimestepMultiple(10) + + ## We must now create one or more update rules, which determine the Hamiltonian in the Potts simulation. + ## For this test, we use two update rules based upon a volume constraint (VolumeConstraintPottsUpdateRule) and + ## differential adhesion between cells (DifferentialAdhesionPottsUpdateRule), set appropriate parameters, and + ## pass them to the OnLatticeSimulation. + + volume_constraint_update_rule = chaste.cell_based.VolumeConstraintPottsUpdateRule[2]() + volume_constraint_update_rule.SetMatureCellTargetVolume(16) + volume_constraint_update_rule.SetDeformationEnergyParameter(0.2) + simulator.AddUpdateRule(volume_constraint_update_rule) + + ## We repeat the process for any other update rules. + + differential_adhesion_update_rule = chaste.cell_based.DifferentialAdhesionPottsUpdateRule[2]() + differential_adhesion_update_rule.SetLabelledCellLabelledCellAdhesionEnergyParameter(0.16) + differential_adhesion_update_rule.SetLabelledCellCellAdhesionEnergyParameter(0.11) + differential_adhesion_update_rule.SetCellCellAdhesionEnergyParameter(0.02) + differential_adhesion_update_rule.SetLabelledCellBoundaryAdhesionEnergyParameter(0.16) + differential_adhesion_update_rule.SetCellBoundaryAdhesionEnergyParameter(0.16) + simulator.AddUpdateRule(differential_adhesion_update_rule) + + ## To run the simulation, we call `Solve()`. + + simulator.Solve() + + ## The next two lines are for test purposes only and are not part of this tutorial. + ## If different simulation input parameters are being explored the lines should be removed. + + self.assertEqual(cell_population.GetNumRealCells(), 64) + self.assertAlmostEqual(chaste.cell_based.SimulationTime.Instance().GetTime(), 20.0, 6) + + # JUPYTER_TEARDOWN + + ## ### Test 3 - 3D Cell Sorting + ## The next test extends the previous example to three dimensions. + + def test_potts_spheroid_cell_sorting(self): + + # JUPYTER_SETUP + + ## First, we generate a Potts mesh. To create a PottsMesh, we can use the PottsMeshGenerator. + ## This generates a regular square-shaped mesh, in which all elements are the same size. + ## Here the first three arguments specify the domain width; the number of elements across; and the width of elements. + ## The second set of three arguments specify the domain height; the number of elements up; and the height of individual elements. + ## The third set of three arguments specify the domain depth; the number of elements deep; and the depth of individual elements. + ## We have chosen an 4 by 4 by 4 ( = 64) block of elements each consisting of 2 by 2 by 2 ( = 8) lattice sites. + + generator = chaste.mesh.PottsMeshGenerator[3](10, 4, 2, 10, 4, 2, 10, 4, 2) + mesh = generator.GetMesh() + + ## Having created a mesh, we now create a VectorSharedPtrCells. To do this, we the CellsGenerator helper class, + ## as before but this time the third argument is set to make all cells non-proliferative. + + differentiated_type = chaste.cell_based.DifferentiatedCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformCellCycleModel", 3]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), differentiated_type) + + ## As for the 2D case before we make a CellPopulation we make a pointer to a cell label and then assign this label to some randomly chosen cells. + + label = chaste.cell_based.CellLabel() + for eachCell in cells: + if chaste.core.RandomNumberGenerator.Instance().ranf() < 0.5: + eachCell.AddCellProperty(label) + + ## Now we have a mesh and a set of cells to go with it, we can create a CellPopulation. + + cell_population = chaste.cell_based.PottsBasedCellPopulation[3](mesh, cells) + + ## In order to visualize labelled cells we need to use the following command. + + cell_population.AddCellWriterCellLabelWriter() + + ## We then pass in the cell population into an `OffLatticeSimulation`, and set the output directory and end time + + simulator = chaste.cell_based.OnLatticeSimulation[3](cell_population) + simulator.SetOutputDirectory("Python/TestPottsBasedCellSorting3D") + simulator.SetEndTime(20.0) + simulator.SetSamplingTimestepMultiple(10) + + ## We must now create one or more update rules, which determine the Hamiltonian in the Potts simulation. + ## Now set the target volume to be appropriate for this 3D simulation. + + volume_constraint_update_rule = chaste.cell_based.VolumeConstraintPottsUpdateRule[3]() + volume_constraint_update_rule.SetMatureCellTargetVolume(8) + volume_constraint_update_rule.SetDeformationEnergyParameter(0.2) + simulator.AddUpdateRule(volume_constraint_update_rule) + + ## We use the same differential adhesion parameters as in the 2D case. + + differential_adhesion_update_rule = chaste.cell_based.DifferentialAdhesionPottsUpdateRule[3]() + differential_adhesion_update_rule.SetLabelledCellLabelledCellAdhesionEnergyParameter(0.16) + differential_adhesion_update_rule.SetLabelledCellCellAdhesionEnergyParameter(0.11) + differential_adhesion_update_rule.SetCellCellAdhesionEnergyParameter(0.02) + differential_adhesion_update_rule.SetLabelledCellBoundaryAdhesionEnergyParameter(0.16) + differential_adhesion_update_rule.SetCellBoundaryAdhesionEnergyParameter(0.16) + simulator.AddUpdateRule(differential_adhesion_update_rule) + + ## To run the simulation, we call `Solve()`. + + simulator.Solve() + + ## The next two lines are for test purposes only and are not part of this tutorial. + ## If different simulation input parameters are being explored the lines should be removed. + + self.assertEqual(cell_population.GetNumRealCells(), 64) + self.assertAlmostEqual(chaste.cell_based.SimulationTime.Instance().GetTime(), 20.0, 6) + + # JUPYTER_TEARDOWN + + +if __name__ == "__main__": + unittest.main(verbosity=2) + +# endif END_WIKI diff --git a/pychaste/test/tutorial/TestPyScratchAssayTutorial.py b/pychaste/test/tutorial/TestPyScratchAssayTutorial.py new file mode 100644 index 0000000000..0adec6f131 --- /dev/null +++ b/pychaste/test/tutorial/TestPyScratchAssayTutorial.py @@ -0,0 +1,231 @@ +"""Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +# ifndef +# define TRIGGER_WIKI + +## ## Introduction +## This tutorial is an example of modelling a scratch assay using a simple cellular automaton +## representation of cells. It will cover the following techniques: +## +## * Setting up a regular mesh (or lattice) +## * Visualizing the mesh +## * Working with file-based output +## * Generating cells and adding them to the mesh +## * Simulating cell migration on the mesh +## * Real-time visualization of the cell population and plotting of population statistics +## +## ## The Test + +import unittest # Python testing framework + +import chaste # The PyChaste module +import chaste.cell_based # Contains cell populations +import chaste.mesh # Contains meshes +import chaste.visualization # Visualization tools +import matplotlib.pyplot as plt # Plotting +import numpy as np # Matrix tools + + +class TestPyScratchAssayTutorial(chaste.cell_based.AbstractCellBasedTestSuite): + + ## ### Test 1 - Scratch Assay + ## In this test we will create a scratch along the middle of a domain and quantify the migration + ## of cells into the region. Cells will migrate by random walk on the their regular mesh (lattice). + ## + + def test_single_scratch(self): + + # JUPYTER_SETUP + + ## Chaste is based on the concept of `Cells` and `Meshes`. 'Cells' do not store their position in space, + ## or connectivity, these are managed by a `Mesh`. The first step in most Chaste simulations is to + ## set up a mesh, on which we can locate cells. A collection of `Cells` and a `Mesh` are a `CellPopulation` + ## in Chaste terminology. The most simple `CellPopulation` is the `CaBasedCellPopulation` which corresponds + ## to cells occupying discrete locations on a regular mesh (lattice). Our first step is to set up the mesh. + ## Here we set up a 2D lattice. + + num_points_in_x = 100 + num_points_in_y = 12 + generator = chaste.mesh.PottsMeshGenerator[2](num_points_in_x, 0, 0, num_points_in_y, 0, 0) + mesh = generator.GetMesh() + + ## Note that we are using a `PottsMeshGenerator[2]` to set up the grid and we are setting some terms to 0. Chaste + ## design is based on re-use of components, the `PottsMeshGenerator` can be used to set up other types of + ## cell population which require these extra terms. Note also the '[2]' at the end of the class name. This + ## tells us that we are working in 2D. Most Chaste classes are specialized (templated) for spatial dimension, + ## so we need to make sure we are consistent in the dimensionality of the classes we are using. + ## + ## Next we set up some cells. We create and empty container `VectorSharedPtrCell` (which will behave like a Python list) + ## and will fill it with cells of our chosen type. In Chaste cells can be assinged a number of proliferative types + ## (Default, Differentiated, Stem, Transit or User Defined). These types will define how cells behave in certain + ## simulations, for example whether they will proliferate. We just want our cells to migrate in this example, so + ## we set a DifferentiatedCellProliferativeType. + + cells = [] + differentiated_type = chaste.cell_based.DifferentiatedCellProliferativeType() + + ## We are not interested in cell cycling so we specialize the generator to NoCellCycleModel. + + cell_generator = chaste.cell_based.CellsGenerator["NoCellCycleModel", 2]() + + ## We want two sets of cells, starting on opposite sides of the mesh. We use `location_indices` to map cells onto + ## locations (or Nodes) on the mesh. For our regular mesh the Node indices increase fastest in x, then y. We will + ## add four layers of cells to each side of the mesh. + + num_cell_layers = 4 + bottom_location_indices = list(range(num_cell_layers * num_points_in_x)) + num_grid_points = num_points_in_x * num_points_in_y + top_location_indices = list( + range( + num_grid_points - 1, + num_grid_points - num_cell_layers * num_points_in_x - 1, + -1, + ) + ) + cells = cell_generator.GenerateGivenLocationIndices( + bottom_location_indices + top_location_indices, + differentiated_type + ) + + ## Now we have a mesh and a set of cells to go with it, we can create a CellPopulation. + + cell_population = chaste.cell_based.CaBasedCellPopulation[2]( + mesh, + cells, + bottom_location_indices + top_location_indices + ) + + ## Next, we set up an `OffLatticeSimulation` which will manage the solver. We need to add some custom rules to + ## this solver to specify how we want the cells to migrate. + + simulator = chaste.cell_based.OnLatticeSimulation[2](cell_population) + simulator.SetOutputDirectory("Python/TestScratchAssayTutorial") + simulator.SetEndTime(10.0) + simulator.SetDt(0.1) + simulator.SetSamplingTimestepMultiple(1) + + ## We must now create a rule for cell migration. We will use an existing diffusion type rule. + + diffusion_update_rule = chaste.cell_based.DiffusionCaUpdateRule[2]() + simulator.AddUpdateRule(diffusion_update_rule) + + ## PyChaste can do simple 3D rendering with VTK. We set up a `VtkScene` so that we can see the population + ## evovle in real time. + + scene = chaste.visualization.VtkScene[2]() + scene.SetCellPopulation(cell_population) + scene.GetCellPopulationActorGenerator().SetShowCellCentres(True) + # JUPYTER_SHOW_FIRST + scene.Start() # JUPYTER_SHOW + + ## We add the scene to the simulation for real-time updating using a `VtkSceneModifier`. Such + ## modifiers are called by the simulator at regular periods during the main time loop and + ## have access to the cell population. We will use a similar idea in a moment to record cell + ## positions for real time plotting. + + scene_modifier = chaste.cell_based.VtkSceneModifier[2]() + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(10) + simulator.AddSimulationModifier(scene_modifier) + + ## Chaste and PyChaste use object oriented programming. This may require some background reading, + ## but allows for great flexibility in terms of modifying existing functionality. In + ## order to pull the data we want out of the simulation as it runs we will create our own + ## simulation modifier class and use it for real time plotting. This Python class over-rides + ## one of the built-in classes, giving us access to the quantities we want during the simulation. + ## Usually we would define such a class in a different module and import it, it is placed + ## here for the purposes of the tutorial. + + class PlottingModifier(chaste.cell_based.PythonSimulationModifier[2]): + """Class for real time plotting of cell numbers using Matplotlib""" + + def __init__(self, num_points_in_x, num_points_in_y): + super(PlottingModifier, self).__init__() + + # Set up a figure for plotting + plt.ioff() + self.fig = plt.figure() + self.fig.ax = self.fig.add_subplot(111) + self.fig.ax.set_xlabel("y - Position (Cell Lengths)") + self.fig.ax.set_ylabel("Number Of Cells") + self.plot_frequency = 10 # only plot every 10 steps + self.num_points_in_x = num_points_in_x + self.num_points_in_y = num_points_in_y + + def UpdateAtEndOfTimeStep(self, cell_population): + """Plot the number of cells at each lattice point and time-point + + Use the SimulationTime singleton to determine when to plot. + """ + + num_increments = chaste.cell_based.SimulationTime.Instance().GetTimeStepsElapsed() + if num_increments % self.plot_frequency == 0: + y_locations = np.linspace(0, num_points_in_y, num_points_in_y) + num_cells = [] + for idx in range(num_points_in_y): + counter = 0 + for jdx in range(num_points_in_x): + if cell_population.IsCellAttachedToLocationIndex( + jdx + idx * num_points_in_x + ): + counter += 1 + num_cells.append(counter) + + self.fig.ax.plot(y_locations, num_cells, color="black") + self.fig.canvas.draw() + # display.display(self.fig) + # display.clear_output(wait=True) + + def SetupSolve(self, cell_population, output_directory): + """Ensure the cell population is in the correct state at the start of the simulation""" + + cell_population.Update() + + plotting_modifier = PlottingModifier(num_points_in_x, num_points_in_y) + simulator.AddSimulationModifier(plotting_modifier) + + ## To run the simulation, we call `Solve()` and optionally set up interactive plotting. We will see the cells + ## migrate and the population distribution gradually become more uniform. + + scene.Start() + plt.ion() + plt.show() + simulator.Solve() + + # JUPYTER_TEARDOWN + + +if __name__ == "__main__": + unittest.main(verbosity=2) + +# endif END_WIKI diff --git a/pychaste/test/tutorial/TestPySpheroidTutorial.py b/pychaste/test/tutorial/TestPySpheroidTutorial.py new file mode 100644 index 0000000000..9a4344ef9a --- /dev/null +++ b/pychaste/test/tutorial/TestPySpheroidTutorial.py @@ -0,0 +1,174 @@ +"""Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +# ifndef +# define TRIGGER_WIKI + +## ## Introduction +## This tutorial is an example of modelling spheroid growth with a nutrient. +## It covers: +## * Setting up an off-lattice cell population +## * Setting up a cell cycle model with oxygen dependence +## * Setting up and solving an oxygen transport PDE +## * Setting up a cell killer +## +## ## The Test + +import unittest # Python testing framework + +import chaste # The PyChaste module +import chaste.cell_based # Contains cell populations +import chaste.mesh # Contains meshes +import chaste.pde # PDEs +import chaste.visualization # Visualization tools +import matplotlib.pyplot as plt # Plotting +import numpy as np # Matrix tools + + +class TestPySpheroidTutorial(chaste.cell_based.AbstractCellBasedTestSuite): + + ## ### Test 1 - a 2D mesh-based spheroid + ## In this test we set up a spheroid with a plentiful supply of oxygen on the boundary and watch it grow + ## over time. Cells can gradually become apoptotic if the oxygen tension is too low. + + def test_spheroid(self): + + # JUPYTER_SETUP + + ## This time we will use on off-lattice `MeshBased` cell population. Cell centres are joined with + ## springs with a Delauney Triangulation used to identify neighbours. Cell area is given by the dual + ## (Voronoi Tesselation). We start off with a small number of cells. We use a `MutableMesh` which + ## can change connectivity over time and a `HoneycombMeshGenerator` to set it up with a simple + ## honeycomb pattern. Here the first and second arguments define the size of the mesh - + ## we have chosen a mesh that is 5 nodes (i.e. cells) wide, and 5 nodes high. The extra '2' argument puts + ## two layers of non-cell elements around the mesh, which help to form a nicer voronoi tesselation + ## for area calculations. + + chaste.core.OutputFileHandler("Python/TestSpheroidTutorial") + generator = chaste.mesh.HoneycombMeshGenerator(5, 5) + mesh = generator.GetMesh() + + ## We create some cells next, with a stem-like proliferative type. This means they will continually + ## proliferate if there is enough oxygen, similar to how a tumour spheroid may behave. + + stem_type = chaste.cell_based.StemCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["SimpleOxygenBasedCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumNodes(), stem_type) + + ## Define when cells become apoptotic + + for eachCell in cells: + cell_cycle_model = eachCell.GetCellCycleModel() + eachCell.GetCellData().SetItem("oxygen", 30.0) + cell_cycle_model.SetDimension(2) + cell_cycle_model.SetStemCellG1Duration(4.0) + cell_cycle_model.SetHypoxicConcentration(0.1) + cell_cycle_model.SetQuiescentConcentration(0.3) + cell_cycle_model.SetCriticalHypoxicDuration(8) + g1_duration = cell_cycle_model.GetStemCellG1Duration() + sg2m_duration = cell_cycle_model.GetSG2MDuration() + rnum = chaste.core.RandomNumberGenerator.Instance().ranf() + birth_time = -rnum * (g1_duration + sg2m_duration) + eachCell.SetBirthTime(birth_time) + + ## Now we have a mesh and a set of cells to go with it, we can create a `CellPopulation` as before. + + cell_population = chaste.cell_based.MeshBasedCellPopulation[2, 2](mesh, cells) + + ## To view the results of this and the next test in Paraview it is necessary to explicitly generate the required .vtu files. + + cell_population.AddPopulationWriterVoronoiDataWriter() + + ## We then pass in the cell population into an `OffLatticeSimulation`, and set the output directory and end time. + + simulator = chaste.cell_based.OffLatticeSimulation[2, 2](cell_population) + simulator.SetOutputDirectory("Python/TestSpheroidTutorial") + simulator.SetEndTime(5.0) + + ## We ask for output every 12 increments + + simulator.SetSamplingTimestepMultiple(100) + + ## We define how the springs between cells behave using a force law. + + force = chaste.cell_based.GeneralisedLinearSpringForce[2, 2]() + simulator.AddForce(force) + + ## We set up a PDE for oxygen diffusion and consumption by cells, setting the rate of consumption to 0.1 + + pde = chaste.pde.CellwiseSourceEllipticPde[2](cell_population, -0.5) + + ## We set a constant amount of oxygen on the edge of the spheroid + + bc = chaste.pde.ConstBoundaryCondition[2](1.0) + is_neumann_bc = False + + ## Set up a pde modifier to solve the PDE at each simulation time step + + # pde_modifier = chaste.cell_based.EllipticGrowingDomainPdeModifier[2](pde, bc, is_neumann_bc) + # pde_modifier.SetDependentVariableName("oxygen") + # simulator.AddSimulationModifier(pde_modifier) + + ## As before, we set up a scene modifier for real-time visualization + + scene = chaste.visualization.VtkScene[2]() + scene.SetCellPopulation(cell_population) + scene.GetCellPopulationActorGenerator().SetColorByCellData(True) + scene.GetCellPopulationActorGenerator().SetDataLabel("oxygen") + scene.GetCellPopulationActorGenerator().SetShowCellCentres(True) + scene.GetCellPopulationActorGenerator().SetShowVoronoiMeshEdges(False) + # JUPYTER_SHOW_FIRST + + scene_modifier = chaste.cell_based.VtkSceneModifier[2]() + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(100) + simulator.AddSimulationModifier(scene_modifier) + + ## Eventually remove apoptotic cells + + killer = chaste.cell_based.ApoptoticCellKiller[2](cell_population) + simulator.AddCellKiller(killer) + + ## To run the simulation, we call `Solve()`. We can again do a quick rendering of the population at the end of the simulation + + scene.Start() + simulator.Solve() + + # JUPYTER_TEARDOWN + + ## Full results can be visualized in Paraview from the `file_handler.GetOutputDirectoryFullPath()` directory. + + +if __name__ == "__main__": + unittest.main(verbosity=2) + +# endif END_WIKI diff --git a/pychaste/test/tutorial/TestPyTensileTestTutorial.py b/pychaste/test/tutorial/TestPyTensileTestTutorial.py new file mode 100644 index 0000000000..924f6d45cb --- /dev/null +++ b/pychaste/test/tutorial/TestPyTensileTestTutorial.py @@ -0,0 +1,164 @@ +"""Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +# ifndef +# define TRIGGER_WIKI + +## ## Introduction +## In this tutorial we will demonstrate a simulated tensile test on an epithelial sheet. This test +## demonstrates: +## * Working with vertex based off lattice populations +## * Applying boundary conditions +## * Working with forces +## +## ## The Test + +import unittest # Python testing framework + +import chaste # The PyChaste module +import chaste.cell_based # Contains cell populations +import chaste.mesh # Contains meshes +import chaste.visualization # Visualization tools +import numpy as np # Matrix tools + + +class TestPyTensileTestTutorial(chaste.cell_based.AbstractCellBasedTestSuite): + + ## ### Test 1 - A 2D test + + def test_monolayer(self): + + # JUPYTER_SETUP + + ## First, we generate a vertex mesh using a HoneycombVertexMeshGenerator. + + generator = chaste.mesh.HoneycombVertexMeshGenerator(5, 15) + mesh = generator.GetMesh() + + ## Now set up the cells, again we want to avoid proliferation. + + differentiated_type = chaste.cell_based.DifferentiatedCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformG1GenerationalCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), differentiated_type) + + ## Next, create the cell population + + cell_population = chaste.cell_based.VertexBasedCellPopulation[2](mesh, cells) + + ## Pass the cell population into an `OffLatticeSimulation`, and set the output directory, output multiple and end time + + simulator = chaste.cell_based.OffLatticeSimulation[2, 2](cell_population) + simulator.SetOutputDirectory("Python/TestTensileTest") + simulator.SetEndTime(1.0) + simulator.SetSamplingTimestepMultiple(1000) + + ## Now create a force law + + force = chaste.cell_based.NagaiHondaForce[2]() + simulator.AddForce(force) + + ## A `NagaiHondaForce` assumes that each cell has a target area. The target areas of cells are used to determine + ## pressure forces on each vertex and eventually determine the size of each cell in the simulation. + ## In order to assign target areas to cells and update them in each time step we add a `SimpleTargetAreaModifier` + ## to the simulation, which inherits from `AbstractTargetAreaModifier`. + + growth_modifier = chaste.cell_based.SimpleTargetAreaModifier[2]() + simulator.AddSimulationModifier(growth_modifier) + + ## For our tensile test we will fix the bottom of the sheet and subject the top to an applied displacement. We neglect + ## fixing lateral degress of freedom for simplicity, since we are using an over-damped mechanical model. + + my_point = np.array([0.0, 0.0]) + normal = np.array([0.0, -1.0]) + bc = chaste.cell_based.AttractingPlaneBoundaryCondition[2, 2](cell_population, my_point, normal) + simulator.AddCellPopulationBoundaryCondition(bc) + + point = np.array([0.0, 15.5]) + normal = np.array([0.0, -1.0]) + bc2 = chaste.cell_based.AttractingPlaneBoundaryCondition[2, 2](cell_population, point, normal) + simulator.AddCellPopulationBoundaryCondition(bc2) + + ## We want to displace our top boundary over time. We could write a custom boundary condition class to do this. + ## A more simple alternative is to modify the the position of the point describing our boundary plane in `bc2` + ## as the simulation progresses. As per earlier tutorials we make a new `SimulationModifier` class to do this. + + class BoundaryConditionModifier(chaste.cell_based.PythonSimulationModifier[2]): + """Class for time varying boundary conditions""" + + def __init__(self, boundary_condition): + self.boundary_condition = boundary_condition + self.original_location = boundary_condition.rGetPointOnPlane() + self.velocity = 0.5 # cell lengths per time + super(BoundaryConditionModifier, self).__init__() + + def UpdateAtEndOfTimeStep(self, cell_population): + """Move the boundary upwards at the specified velocity""" + + total_time = chaste.cell_based.SimulationTime.Instance().GetTime() + new_location = [ + self.original_location[0], + self.original_location[1] + self.velocity * total_time, + ] + self.boundary_condition.SetPointOnPlane(np.array(new_location)) + + def SetupSolve(self, cell_population, output_directory): + """Make sure the cell population is in the correct state at the start of the simulation""" + + cell_population.Update() + + bc_modifier = BoundaryConditionModifier(bc2) + simulator.AddSimulationModifier(bc_modifier) + + ## PyChaste can do simple 3D rendering with VTK. We set up a `VtkScene` so that we can see the population + ## evovle in real time. + + scene = chaste.visualization.VtkScene[2]() + scene.SetCellPopulation(cell_population) + # JUPYTER_SHOW_FIRST + + scene_modifier = chaste.cell_based.VtkSceneModifier[2]() + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(1000) + simulator.AddSimulationModifier(scene_modifier) + + ## To run the simulation, we call `Solve()`. + + scene.Start() + simulator.Solve() + + # JUPYTER_TEARDOWN + + +if __name__ == "__main__": + unittest.main(verbosity=2) + +# endif END_WIKI diff --git a/pychaste/test/tutorial/TestPyVertexBasedCellSimulationsTutorial.py b/pychaste/test/tutorial/TestPyVertexBasedCellSimulationsTutorial.py new file mode 100644 index 0000000000..9733beef21 --- /dev/null +++ b/pychaste/test/tutorial/TestPyVertexBasedCellSimulationsTutorial.py @@ -0,0 +1,235 @@ +"""Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +# ifndef +# define TRIGGER_WIKI + +## ## Introduction +## In this tutorial we show how Chaste can be used to create, run and visualize vertex-based simulations. +## Full details of the mechanical model proposed by T. Nagai and H. Honda ("A dynamic cell model for the formation of epithelial tissues", +## Philosophical Magazine Part B 81:699-719). +## +## ## The Test + +import unittest # Python testing framework + +import matplotlib.pyplot as plt # Plotting +import numpy as np # Matrix tools + +import chaste # The PyChaste module +import chaste.cell_based # Contains cell populations +import chaste.mesh # Contains meshes +import chaste.visualization # Visualization tools + +from chaste.cell_based import AbstractCellBasedTestSuite + + +class TestPyVertexBasedCellSimulationsTutorial(AbstractCellBasedTestSuite): + ## ### Test 1 - A basic vertex-based simulation + ## In the first test, we run a simple vertex-based simulation, in which we create a monolayer of cells, + ## using a mutable vertex mesh. Each cell is assigned a stochastic cell-cycle model. + + def test_monolayer(self): + + # JUPYTER_SETUP + + ## First, we generate a vertex mesh. To create a MutableVertexMesh, we can use the HoneycombVertexMeshGenerator. + ## This generates a honeycomb-shaped mesh, in which all nodes are equidistant. Here the first and second arguments + ## define the size of the mesh - we have chosen a mesh that is 2 elements (i.e. cells) wide, and 2 elements high. + + chaste.core.OutputFileHandler("Python/TestVertexBasedCellSimulationsTutorial") + generator = chaste.mesh.HoneycombVertexMeshGenerator(2, 2) + mesh = generator.GetMesh() + + ## Having created a mesh, we now create a std::vector of CellPtrs. To do this, we use the CellsGenerator helper class, + ## which is templated over the type of cell model required + ## and the dimension. We create an empty vector of cells and pass this into the method along with the mesh. + ## The second argument represents the size of that the vector cells should become - one cell for each element, + ## the third argument specifies the proliferative type of the cell. + + transit_type = chaste.cell_based.TransitCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformG1GenerationalCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), transit_type) + + ## Now we have a mesh and a set of cells to go with it, we can create a CellPopulation. + ## In general, this class associates a collection of cells with a mesh. For this test, because we have a MutableVertexMesh, + ## we use a particular type of cell population called a VertexBasedCellPopulation. + + cell_population = chaste.cell_based.VertexBasedCellPopulation[2](mesh, cells) + + ## We can set up a `VtkScene` to do a quick visualization of the population before running the analysis. + + scene = chaste.visualization.VtkScene[2]() + scene.SetCellPopulation(cell_population) + # JUPYTER_SHOW_FIRST + scene.Start() # JUPYTER_SHOW + + ## We then pass in the cell population into an `OffLatticeSimulation`, and set the output directory, output multiple and end time + + simulator = chaste.cell_based.OffLatticeSimulation[2, 2](cell_population) + simulator.SetOutputDirectory("Python/TestVertexBasedCellSimulationsTutorial") + simulator.SetEndTime(5.0) + + ## For longer simulations, we may not want to output the results every time step. + ## In this case we can use the following method, to print results every 50 time steps instead. + ## As the default time step used by the simulator (for vertex based simulations), is 0.02 hours, this method + ## will cause the simulator to print results every 6 minutes (i.e. 0.1 hours). + + simulator.SetSamplingTimestepMultiple(50) + + ## We must now create one or more force laws, which determine the mechanics of the vertices of each cell in a cell population. + ## For this test, we use one force law, based on the Nagai-Honda mechanics, and pass it to the OffLatticeSimulation. + ## For a list of possible forces see subclasses of AbstractForce. + ## Note that some of these forces are not compatible with vertex-based simulations see the specific class documentation for details, + ## if you try to use an incompatible class then you will receive a warning. + + force = chaste.cell_based.NagaiHondaForce[2]() + simulator.AddForce(force) + + ## A NagaiHondaForce assumes that each cell has a target area. The target areas of cells are used to determine + ## pressure forces on each vertex and eventually determine the size of each cell in the simulation. + ## In order to assign target areas to cells and update them in each time step we add a SimpleTargetAreaModifier + ## to the simulation, which inherits from AbstractTargetAreaModifier. + + growth_modifier = chaste.cell_based.SimpleTargetAreaModifier[2]() + simulator.AddSimulationModifier(growth_modifier) + + ## Save snapshot images of the population during the simulation + + scene_modifier = chaste.cell_based.VtkSceneModifier[2]() + scene_modifier.SetVtkScene(scene) + scene_modifier.SetUpdateFrequency(100) + simulator.AddSimulationModifier(scene_modifier) + + ## To run the simulation, we call `Solve()`. We can again do a quick rendering of the population at the end of the simulation + + scene.Start() + simulator.Solve() + scene.End() + + ## The next two lines are for test purposes only and are not part of this tutorial. + ## If different simulation input parameters are being explored the lines should be removed. + + self.assertEqual(cell_population.GetNumRealCells(), 7) + self.assertAlmostEqual(chaste.cell_based.SimulationTime.Instance().GetTime(), 5.0, 6) + + # JUPYTER_TEARDOWN + + ## ### Test 2 - introducing periodicity, boundaries and cell killers + ## In the second test, we run a simple vertex-based simulation, in which we create a monolayer of cells in a periodic geometry, + ## using a cylindrical vertex mesh. We also include a fixed boundary which cells can't pass through and a cell killer which removes + ## cells once they leave a region. As before each cell is assigned a stochastic cell-cycle model. + + def test_periodic_monolayer(self): + + # JUPYTER_SETUP + + ## First, we generate a periodic vertex mesh. To create a Cylindrical2dVertexMesh, we can use the CylindricalHoneycombVertexMeshGenerator. + ## This generates a honeycomb-shaped mesh, in which all nodes are equidistant and the right hand side is associated with the left hand side. + ## Here the first and second arguments define the size of the mesh - we have chosen a mesh that is + ## 4 elements (i.e. cells) wide, and 4 elements high. + + generator = chaste.mesh.CylindricalHoneycombVertexMeshGenerator(4, 4) + mesh = generator.GetCylindricalMesh() + + ## Having created a mesh, we now create a VectorSharedPtrCells. This is exactly the same as the above test. + + transit_type = chaste.cell_based.TransitCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformG1GenerationalCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom(mesh.GetNumElements(), transit_type) + + ## Now we have a mesh and a set of cells to go with it, we can create a CellPopulation. This is also the same as in the above test. + + cell_population = chaste.cell_based.VertexBasedCellPopulation[2](mesh, cells) + + ## We then pass in the cell population into an `OffLatticeSimulation`, and set the output directory, output multiple and end time + + simulator = chaste.cell_based.OffLatticeSimulation[2, 2](cell_population) + simulator.SetOutputDirectory("Python/TestPeriodicVertexBasedCellPopulation") + simulator.SetEndTime(1.0) + simulator.SetSamplingTimestepMultiple(50) + + ## We now make a pointer to an appropriate force and pass it to the OffLatticeSimulation. + + force = chaste.cell_based.NagaiHondaForce[2]() + simulator.AddForce(force) + + ## We also make a pointer to the target area modifier and add it to the simulator. + + growth_modifier = chaste.cell_based.SimpleTargetAreaModifier[2]() + simulator.AddSimulationModifier(growth_modifier) + + ## We now create one or more CellPopulationBoundaryConditions, which determine any conditions which each cell in a cell population must satisfy. + ## For this test, we use a PlaneBoundaryCondition, and pass it to the OffLatticeSimulation. For a list of possible boundary condition + ## see subclasses of AbstractCellPopulationBoundaryCondition. + ## Note that some of these boundary conditions are not compatible with vertex-based simulations see the specific class documentation + ## for details, if you try to use an incompatible class then you will receive a warning. + ## The first step is to define a point on the plane boundary and a normal to the plane. + + point = np.array([0.0, 0.0]) + normal = np.array([0.0, -1.0]) + + ## We can now make a PlaneBoundaryCondition (passing the point and normal to the plane) and pass it to the OffLatticeSimulation. + + bc = chaste.cell_based.PlaneBoundaryCondition[2, 2](cell_population, point, normal) + simulator.AddCellPopulationBoundaryCondition(bc) + + ## We now create one or more CellKillers, which determine how cells are removed from the simulation. + ## For this test, we use a PlaneBasedCellKiller, and pass it to the OffLatticeSimulation. + ## For a list of possible cell killers see subclasses of AbstractCellKiller. + ## The first step is to define a point on the plane boundary and a normal to the plane. + + point = np.array([0.0, 3.0]) + normal = np.array([0.0, 1.0]) + + ## Finally we now make a PlaneBasedCellKiller (passing the point and normal to the plane) and pass it to the OffLatticeSimulation. + + killer = chaste.cell_based.PlaneBasedCellKiller[2](cell_population, point, normal) + simulator.AddCellKiller(killer) + + ## To run the simulation, we call `Solve()`. + + simulator.Solve() + + ## The next two lines are for test purposes only and are not part of this tutorial. + ## If different simulation input parameters are being explored the lines should be removed. + + self.assertEqual(cell_population.GetNumRealCells(), 12) + self.assertAlmostEqual(chaste.cell_based.SimulationTime.Instance().GetTime(), 1.0, 6) + + # JUPYTER_TEARDOWN + + +if __name__ == "__main__": + unittest.main(verbosity=2) + +# endif END_WIKI diff --git a/pychaste/test/visualization/TestPyJupyterNotebookManager.py b/pychaste/test/visualization/TestPyJupyterNotebookManager.py new file mode 100644 index 0000000000..011642e958 --- /dev/null +++ b/pychaste/test/visualization/TestPyJupyterNotebookManager.py @@ -0,0 +1,75 @@ +"""Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import unittest + +import chaste +import chaste.cell_based +import chaste.mesh +import chaste.visualization + + +class TestPyJupyterNotebookManager(chaste.cell_based.AbstractCellBasedTestSuite): + def test_vtk_show(self): + file_handler = chaste.core.OutputFileHandler( + "Python/TestVertexBasedCellPopulation" + ) + + # Set up the mesh + mesh_generator = chaste.mesh.HoneycombVertexMeshGenerator(2, 2) + mesh = mesh_generator.GetMesh() + + # Make the cells + proliferative_type = chaste.cell_based.DefaultCellProliferativeType() + cell_generator = chaste.cell_based.CellsGenerator["UniformCellCycleModel", 2]() + cells = cell_generator.GenerateBasicRandom( + mesh.GetNumElements(), proliferative_type + ) + + # Make the cell population + cell_population = chaste.cell_based.VertexBasedCellPopulation[2](mesh, cells) + + # Test multiple calls to the visualizer + for _ in range(10): + scene = chaste.visualization.VtkScene[2]() + scene.SetCellPopulation(cell_population) + scene.SetSaveAsAnimation(True) + scene.SetOutputFilePath( + file_handler.GetOutputDirectoryFullPath() + "/cell_population" + ) + + nb_manager = chaste.visualization.JupyterNotebookManager() + nb_manager.vtk_show(scene, height=300) + + +if __name__ == "__main__": + unittest.main() diff --git a/pychaste/test/visualization/TestVtkSceneWithCaBased.hpp b/pychaste/test/visualization/TestVtkSceneWithCaBased.hpp new file mode 100644 index 0000000000..650ca18da4 --- /dev/null +++ b/pychaste/test/visualization/TestVtkSceneWithCaBased.hpp @@ -0,0 +1,155 @@ +/* + +Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TEST_VTK_SCENE_WITH_CA_BASED_HPP_ +#define TEST_VTK_SCENE_WITH_CA_BASED_HPP_ + +#include +#include "CheckpointArchiveTypes.hpp" +#include "AbstractCellBasedTestSuite.hpp" +#include "PottsMeshGenerator.hpp" +#include "Cell.hpp" +#include "CellsGenerator.hpp" +#include "FixedG1GenerationalCellCycleModel.hpp" +#include "UniformCellCycleModel.hpp" +#include "CaBasedCellPopulation.hpp" +#include "CellMutationStatesCountWriter.hpp" +#include "CellProliferativeTypesCountWriter.hpp" +#include "CellProliferativePhasesCountWriter.hpp" +#include "CellProliferativePhasesWriter.hpp" +#include "CellAncestorWriter.hpp" +#include "CellAgesWriter.hpp" +#include "CellVolumesWriter.hpp" +#include "CellMutationStatesWriter.hpp" +#include "OnLatticeSimulation.hpp" +#include "SmartPointers.hpp" +#include "VtkScene.hpp" +#include "VtkSceneModifier.hpp" +#include "FileFinder.hpp" +#include "OutputFileHandler.hpp" +#include "HoneycombMeshGenerator.hpp" +#include "OffLatticeSimulation.hpp" +#include "MeshBasedCellPopulation.hpp" +#include "PottsBasedCellPopulation.hpp" +#include "MeshBasedCellPopulationWithGhostNodes.hpp" +#include "GeneralisedLinearSpringForce.hpp" +#include "VoronoiDataWriter.hpp" +#include "AdhesionPottsUpdateRule.hpp" +#include "VolumeConstraintPottsUpdateRule.hpp" + +#include "PetscSetupAndFinalize.hpp" + +class TestVtkSceneWithCaBasedPopulation : public AbstractCellBasedTestSuite +{ +public: + + void Test2dCaBasedPopulation() + { + OutputFileHandler file_handler = OutputFileHandler("TestVtkSceneWithCaBasedPopulation/2d"); + + PottsMeshGenerator<2> generator(10, 0, 0, 10, 0, 0); + boost::shared_ptr > p_mesh = generator.GetMesh(); + + // Fill with cells + std::vector location_indices; + for(unsigned idx=0; idx<5; idx++) + { + location_indices.push_back(idx); + } + + std::vector cells; + CellsGenerator cells_generator; + cells_generator.GenerateBasic(cells, location_indices.size()); + + // Create cell population + auto p_cell_population = boost::make_shared >(*p_mesh, cells, location_indices); + + auto p_scene = boost::make_shared >(); + p_scene->SetCellPopulation(p_cell_population); + p_scene->SetSaveAsImages(true); + p_scene->SetOutputFilePath(file_handler.GetOutputDirectoryFullPath()+"/cell_population"); + p_scene->GetCellPopulationActorGenerator()->SetShowPottsMeshEdges(true); + + auto p_scene_modifier = boost::make_shared >(); + p_scene_modifier->SetVtkScene(p_scene); + + p_scene->Start(); + OnLatticeSimulation<2> simulator(*p_cell_population); + simulator.SetOutputDirectory("TestVtkSceneWithCaBasedPopulation/2d"); + simulator.SetDt(10.0); + simulator.SetEndTime(40.0); + simulator.AddSimulationModifier(p_scene_modifier); + simulator.Solve(); + } + + void Test3dCaBasedPopulation() + { + OutputFileHandler file_handler1 = OutputFileHandler("TestVtkSceneWithCaBasedPopulation/3d"); + PottsMeshGenerator<3> generator(10, 0, 0, 10, 0, 0, 3, 0, 0); + boost::shared_ptr > p_mesh = generator.GetMesh(); + std::vector location_indices; + for(unsigned idx=0; idx<100; idx++) + { + location_indices.push_back(idx); + } + + std::vector cells; + CellsGenerator cells_generator; + cells_generator.GenerateBasic(cells, location_indices.size()); + + // Create cell population + auto p_cell_population = boost::make_shared >(*p_mesh, cells, location_indices); + + auto p_scene = boost::make_shared >(); + p_scene->SetCellPopulation(p_cell_population); + p_scene->SetSaveAsImages(true); + p_scene->GetCellPopulationActorGenerator()->SetShowPottsMeshEdges(true); + p_scene->SetOutputFilePath(file_handler1.GetOutputDirectoryFullPath()+"/cell_population"); + + auto p_scene_modifier = boost::make_shared >(); + p_scene_modifier->SetVtkScene(p_scene); + + p_scene->Start(); + OnLatticeSimulation<3> simulator(*p_cell_population); + simulator.SetOutputDirectory("TestVtkSceneWithCaBasedPopulation/3d"); + simulator.SetDt(10.0); + simulator.SetEndTime(10.0); + simulator.AddSimulationModifier(p_scene_modifier); + simulator.Solve(); + } + +}; + +#endif //TEST_VTK_SCENE_WITH_CA_BASED_HPP_ diff --git a/pychaste/test/visualization/TestVtkSceneWithMeshBased.hpp b/pychaste/test/visualization/TestVtkSceneWithMeshBased.hpp new file mode 100644 index 0000000000..d6559cf296 --- /dev/null +++ b/pychaste/test/visualization/TestVtkSceneWithMeshBased.hpp @@ -0,0 +1,157 @@ +/* + +Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TESTVTKSCENEWITHMESHBASEDPOPULATION_HPP_ +#define TESTVTKSCENEWITHMESHBASEDPOPULATION_HPP_ + +#include +#include "CheckpointArchiveTypes.hpp" +#include "AbstractCellBasedTestSuite.hpp" +#include "PottsMeshGenerator.hpp" +#include "Cell.hpp" +#include "CellsGenerator.hpp" +#include "FixedG1GenerationalCellCycleModel.hpp" +#include "UniformCellCycleModel.hpp" +#include "CaBasedCellPopulation.hpp" +#include "CellMutationStatesCountWriter.hpp" +#include "CellProliferativeTypesCountWriter.hpp" +#include "CellProliferativePhasesCountWriter.hpp" +#include "CellProliferativePhasesWriter.hpp" +#include "CellAncestorWriter.hpp" +#include "CellAgesWriter.hpp" +#include "CellVolumesWriter.hpp" +#include "CellMutationStatesWriter.hpp" +#include "OnLatticeSimulation.hpp" +#include "SmartPointers.hpp" +#include "VtkScene.hpp" +#include "VtkSceneModifier.hpp" +#include "FileFinder.hpp" +#include "OutputFileHandler.hpp" +#include "HoneycombMeshGenerator.hpp" +#include "OffLatticeSimulation.hpp" +#include "MeshBasedCellPopulation.hpp" +#include "PottsBasedCellPopulation.hpp" +#include "MeshBasedCellPopulationWithGhostNodes.hpp" +#include "GeneralisedLinearSpringForce.hpp" +#include "VoronoiDataWriter.hpp" +#include "AdhesionPottsUpdateRule.hpp" +#include "VolumeConstraintPottsUpdateRule.hpp" + +#include "PetscSetupAndFinalize.hpp" + +class TestVtkSceneWithMeshBasedPopulation : public AbstractCellBasedTestSuite +{ +public: + + void TestRenderingMeshBasedPopulation() + { + OutputFileHandler file_handler1 = OutputFileHandler("TestVtkSceneWithMeshBasedPopulation/2d/"); + + HoneycombMeshGenerator generator(2, 2, 2); // Parameters are: cells across, cells up + boost::shared_ptr > p_mesh = generator.GetMesh(); + std::vector location_indices = generator.GetCellLocationIndices(); + + std::vector cells; + MAKE_PTR(TransitCellProliferativeType, p_transit_type); + CellsGenerator cells_generator; + cells_generator.GenerateBasicRandom(cells, location_indices.size(), p_transit_type); + auto p_cell_population = boost::make_shared >(*p_mesh, cells, location_indices); + p_cell_population->AddPopulationWriter(); + + auto p_scene = boost::make_shared >(); + p_scene->SetCellPopulation(p_cell_population); + p_scene->SetSaveAsImages(true); + p_scene->SetOutputFilePath(file_handler1.GetOutputDirectoryFullPath()+"/cell_population"); + + auto p_scene_modifier = boost::make_shared >(); + p_scene_modifier->SetVtkScene(p_scene); + p_scene->Start(); + + OffLatticeSimulation<2> simulator(*p_cell_population); + simulator.SetOutputDirectory("TestVtkSceneWithMeshBasedPopulation/2d/"); + simulator.SetEndTime(4.0); + simulator.SetSamplingTimestepMultiple(12); + + MAKE_PTR(GeneralisedLinearSpringForce<2>, p_force); + simulator.AddForce(p_force); + simulator.AddSimulationModifier(p_scene_modifier); + simulator.Solve(); + } + + void TestRendering3dMeshBasedPopulation() + { + OutputFileHandler file_handler1 = OutputFileHandler("TestVtkSceneWithMeshBasedPopulation/3d/"); + + std::vector*> nodes; + nodes.push_back(new Node<3>(0, true, 0.0, 0.0, 0.0)); + nodes.push_back(new Node<3>(1, true, 1.0, 1.0, 0.0)); + nodes.push_back(new Node<3>(2, true, 1.0, 0.0, 1.0)); + nodes.push_back(new Node<3>(3, true, 0.0, 1.0, 1.0)); + nodes.push_back(new Node<3>(4, false, 0.5, 0.5, 0.5)); + MutableMesh<3,3> mesh(nodes); + + std::vector cells; + MAKE_PTR(TransitCellProliferativeType, p_transit_type); + CellsGenerator cells_generator; + cells_generator.GenerateBasicRandom(cells, mesh.GetNumNodes(), p_transit_type); + + auto p_cell_population = boost::make_shared >(mesh, cells); + p_cell_population->SetAbsoluteMovementThreshold(DBL_MAX); + p_cell_population->AddPopulationWriter(); + + auto p_scene = boost::make_shared >(); + p_scene->SetCellPopulation(p_cell_population); + p_scene->SetSaveAsImages(true); + p_scene->SetOutputFilePath(file_handler1.GetOutputDirectoryFullPath()+"/cell_population"); + + auto p_scene_modifier = boost::make_shared >(); + p_scene_modifier->SetVtkScene(p_scene); + p_scene->Start(); + + OffLatticeSimulation<3> simulator(*p_cell_population); + simulator.SetOutputDirectory("TestVtkSceneWithMeshBasedPopulation/3d/"); + simulator.SetEndTime(1.0); + simulator.SetDt(0.1); + simulator.SetSamplingTimestepMultiple(100); + + MAKE_PTR(GeneralisedLinearSpringForce<3>, p_force); + p_force->SetMeinekeSpringStiffness(30.0); // default is 15.0; + p_force->SetCutOffLength(1.5); + simulator.AddForce(p_force); + simulator.AddSimulationModifier(p_scene_modifier); + simulator.Solve(); + } +}; +#endif diff --git a/pychaste/test/visualization/TestVtkSceneWithPottsBased.hpp b/pychaste/test/visualization/TestVtkSceneWithPottsBased.hpp new file mode 100644 index 0000000000..82e235a026 --- /dev/null +++ b/pychaste/test/visualization/TestVtkSceneWithPottsBased.hpp @@ -0,0 +1,166 @@ +/* + +Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TESTVTKSCENEWITHPOTTSBASEDPOPAULTION_HPP_ +#define TESTVTKSCENEWITHPOTTSBASEDPOPAULTION_HPP_ + +#include +#include "CheckpointArchiveTypes.hpp" +#include "AbstractCellBasedTestSuite.hpp" +#include "PottsMeshGenerator.hpp" +#include "Cell.hpp" +#include "CellsGenerator.hpp" +#include "FixedG1GenerationalCellCycleModel.hpp" +#include "UniformCellCycleModel.hpp" +#include "CaBasedCellPopulation.hpp" +#include "CellMutationStatesCountWriter.hpp" +#include "CellProliferativeTypesCountWriter.hpp" +#include "CellProliferativePhasesCountWriter.hpp" +#include "CellProliferativePhasesWriter.hpp" +#include "CellAncestorWriter.hpp" +#include "CellAgesWriter.hpp" +#include "CellVolumesWriter.hpp" +#include "CellMutationStatesWriter.hpp" +#include "OnLatticeSimulation.hpp" +#include "SmartPointers.hpp" +#include "VtkScene.hpp" +#include "VtkSceneModifier.hpp" +#include "FileFinder.hpp" +#include "OutputFileHandler.hpp" +#include "HoneycombMeshGenerator.hpp" +#include "OffLatticeSimulation.hpp" +#include "MeshBasedCellPopulation.hpp" +#include "PottsBasedCellPopulation.hpp" +#include "MeshBasedCellPopulationWithGhostNodes.hpp" +#include "GeneralisedLinearSpringForce.hpp" +#include "VoronoiDataWriter.hpp" +#include "AdhesionPottsUpdateRule.hpp" +#include "VolumeConstraintPottsUpdateRule.hpp" + +#include "PetscSetupAndFinalize.hpp" + +class TestVtkScene : public AbstractCellBasedTestSuite +{ +public: + + void TestRenderingPottsBasedPopulation() + { + OutputFileHandler file_handler1 = OutputFileHandler("TestVtkSceneWithMeshBasedPopulation/2d/"); + + PottsMeshGenerator<2> generator(50, 2, 4, 50, 2, 4); + boost::shared_ptr > p_mesh = generator.GetMesh(); + + std::vector cells; + MAKE_PTR(TransitCellProliferativeType, p_transit_type); + CellsGenerator cells_generator; + cells_generator.GenerateBasicRandom(cells, p_mesh->GetNumElements(), p_transit_type); + + auto p_cell_population = boost::make_shared >(*p_mesh, cells); + p_cell_population->SetTemperature(0.1); + p_cell_population->SetNumSweepsPerTimestep(1); + + OnLatticeSimulation<2> simulator(*p_cell_population); + simulator.SetOutputDirectory("TestVtkSceneWithMeshBasedPopulation/2d/"); + simulator.SetEndTime(10.0); + simulator.SetDt(0.1); + simulator.SetSamplingTimestepMultiple(10); + + auto p_scene = boost::make_shared >(); + p_scene->SetCellPopulation(p_cell_population); + p_scene->SetIsInteractive(true); + p_scene->SetSaveAsImages(false); + p_scene->GetCellPopulationActorGenerator()->SetShowPottsMeshEdges(true); + p_scene->SetOutputFilePath(file_handler1.GetOutputDirectoryFullPath()+"/cell_population"); + + auto p_scene_modifier = boost::make_shared >(); + p_scene_modifier->SetVtkScene(p_scene); + p_scene_modifier->SetUpdateFrequency(10); + p_scene->Start(); + + MAKE_PTR(VolumeConstraintPottsUpdateRule<2>, p_volume_constraint_update_rule); + p_volume_constraint_update_rule->SetMatureCellTargetVolume(16); + p_volume_constraint_update_rule->SetDeformationEnergyParameter(0.2); + simulator.AddUpdateRule(p_volume_constraint_update_rule); + MAKE_PTR(AdhesionPottsUpdateRule<2>, p_adhesion_update_rule); + simulator.AddUpdateRule(p_adhesion_update_rule); + simulator.AddSimulationModifier(p_scene_modifier); + simulator.Solve(); + } + + void TestRenderingPottsBasedPopulation3d() + { + OutputFileHandler file_handler1 = OutputFileHandler("TestVtkSceneWithMeshBasedPopulation/3d/"); + + PottsMeshGenerator<3> generator(50, 2, 4, 50, 2, 4, 50, 2, 4); + boost::shared_ptr > p_mesh = generator.GetMesh(); + + std::vector cells; + MAKE_PTR(TransitCellProliferativeType, p_transit_type); + CellsGenerator cells_generator; + cells_generator.GenerateBasicRandom(cells, p_mesh->GetNumElements(), p_transit_type); + + auto p_cell_population = boost::make_shared >(*p_mesh, cells); + p_cell_population->SetTemperature(0.1); + p_cell_population->SetNumSweepsPerTimestep(1); + + OnLatticeSimulation<3> simulator(*p_cell_population); + simulator.SetOutputDirectory("TestVtkSceneWithMeshBasedPopulation/3d/"); + simulator.SetEndTime(1.0); + simulator.SetDt(0.1); + simulator.SetSamplingTimestepMultiple(100); + + auto p_scene = boost::make_shared >(); + p_scene->SetCellPopulation(p_cell_population); + p_scene->SetIsInteractive(true); + p_scene->SetSaveAsImages(false); + p_scene->GetCellPopulationActorGenerator()->SetShowCellCentres(true); + p_scene->GetCellPopulationActorGenerator()->SetShowPottsMeshOutlines(true); + p_scene->SetOutputFilePath(file_handler1.GetOutputDirectoryFullPath()+"/cell_population"); + + auto p_scene_modifier = boost::make_shared >(); + p_scene_modifier->SetVtkScene(p_scene); + p_scene->Start(); + + MAKE_PTR(VolumeConstraintPottsUpdateRule<3>, p_volume_constraint_update_rule); + p_volume_constraint_update_rule->SetMatureCellTargetVolume(16); + p_volume_constraint_update_rule->SetDeformationEnergyParameter(0.2); + simulator.AddUpdateRule(p_volume_constraint_update_rule); + MAKE_PTR(AdhesionPottsUpdateRule<3>, p_adhesion_update_rule); + simulator.AddUpdateRule(p_adhesion_update_rule); + simulator.AddSimulationModifier(p_scene_modifier); + simulator.Solve(); + } +}; +#endif diff --git a/python/infra/CheckForCopyrights.py b/python/infra/CheckForCopyrights.py index 7be2698fa6..cac5ab8994 100755 --- a/python/infra/CheckForCopyrights.py +++ b/python/infra/CheckForCopyrights.py @@ -173,6 +173,8 @@ def AddHashesToBeginningOfLines(message): # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details.""" opensimplex_notice = """* K.jpg's OpenSimplex 2, smooth variant ("SuperSimplex")""" +dolfinx_notice = """// This file is part of DOLFINx (https://www.fenicsproject.org)""" +smtk_notice = """// Copyright (c) Kitware, Inc.""" def CheckForCopyrightNotice(findStrOrRe, fileIn): """Test if the (possibly multi-line) string/regexp findStr is contained anywhere in fileIn.""" @@ -228,8 +230,8 @@ def InspectFile(fileName): if fileName[-21:] == 'CheckForCopyrights.py': # Can't really check this one, since it knows all the licences return True - if "codegen_python3_venv" in fileName: - # Can't really check the codegen virtual environment as it would start checking 3rd party packages + if "chaste_python3_venv" in fileName: + # Can't really check the chaste virtual environment as it would start checking 3rd party packages return True valid_notice = False if (CheckForCopyrightNotice(cpp_current_notice, file_in) or @@ -247,7 +249,9 @@ def InspectFile(fileName): CheckForCopyrightNotice(tetgen_predicates_notice, file_in) or CheckForCopyrightNotice(tetgen_notice, file_in) or CheckForCopyrightNotice(py_lgpl_notice, file_in) or - CheckForCopyrightNotice(opensimplex_notice, file_in)): + CheckForCopyrightNotice(opensimplex_notice, file_in) or + CheckForCopyrightNotice(smtk_notice, file_in) or + CheckForCopyrightNotice(dolfinx_notice, file_in)): # print('Found 3rd party notice in %s' % file_name) if valid_notice: print("Multiple notices on %s" % file_name) @@ -291,7 +295,7 @@ def InspectFile(fileName): def ignore_dir(dir_to_check): - dir_ignores = ['Debug', 'Release', 'build', 'cxxtest', 'codegen_python3_venv', + dir_ignores = ['Debug', 'Release', 'build', 'cxxtest', 'chaste_python3_venv', 'testoutput', 'doc', 'projects', 'hierwikiplugin'] dir_ignore_contains = ['Debug_', 'chaste-build', 'cmake-build', 'venv'] diff --git a/python/infra/CheckForDuplicateFileNames.py b/python/infra/CheckForDuplicateFileNames.py index 1338925ea8..d190daea9a 100755 --- a/python/infra/CheckForDuplicateFileNames.py +++ b/python/infra/CheckForDuplicateFileNames.py @@ -38,7 +38,7 @@ # that have the same name. exts = ['.cpp', '.hpp'] -dir_ignores = ['build', 'dynamic', 'data', 'codegen_python3_venv'] +dir_ignores = ['build', 'dynamic', 'data', 'chaste_python3_venv'] startchar_ignores = ['_', '.'] chaste_dir = '.' diff --git a/python/infra/CheckForOrphanedTests.py b/python/infra/CheckForOrphanedTests.py index a61c42d2c3..0bcd228036 100755 --- a/python/infra/CheckForOrphanedTests.py +++ b/python/infra/CheckForOrphanedTests.py @@ -50,7 +50,7 @@ chaste_dir = '.' suite_res = {'.hpp': re.compile(r'class\s+(\w+)\s*:\s*public\s+((::)?\s*CxxTest\s*::\s*)?\w*TestSuite\s+$'), - '.py': re.compile(r'class\s+\w+\(unittest\.TestCase\):\s+$')} + '.py': re.compile(r'class\s+\w+\s*\((unittest\.TestCase|(.*\.)?AbstractCellBasedTestSuite)\):\s*$')} # Should we check for orphans in projects too? projects_to_check = sys.argv[1:] diff --git a/python/infra/GeneratePyChasteTutorials.py b/python/infra/GeneratePyChasteTutorials.py new file mode 100644 index 0000000000..428a239d71 --- /dev/null +++ b/python/infra/GeneratePyChasteTutorials.py @@ -0,0 +1,492 @@ +"""Copyright (c) 2005-2024, University of Oxford. +All rights reserved. + +University of Oxford means the Chancellor, Masters and Scholars of the +University of Oxford, having an administrative office at Wellington +Square, Oxford OX1 2JD, UK. + +This file is part of Chaste. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the University of Oxford nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import os +import re +import subprocess +import warnings +from pathlib import Path +from subprocess import CalledProcessError, check_output +from typing import List, Tuple + +import nbformat +from nbformat import NotebookNode +from nbformat.v4 import new_code_cell, new_markdown_cell, new_notebook + +CHASTE_SRC_DIR = Path(__file__).resolve().parent.parent.parent +PYCHASTE_SRC_DIR = CHASTE_SRC_DIR / "pychaste" +PYCHASTE_TUTORIAL_DIR = PYCHASTE_SRC_DIR / "src" / "py" / "doc" / "tutorial" +HOWTO_TAG = "HOW_TO_TAG" + +# Status codes for use during parsing +ST_NONE, ST_TEXT, ST_CODE, ST_HOWTO = 0, 1, 2, 3 + +# Placeholder strings for notebook tutorials +JUPYTER_SETUP = "chaste.cell_based.SetupNotebookTest() # Set up the test" +JUPYTER_TEARDOWN = "chaste.cell_based.TearDownNotebookTest() # Tear down the test" +JUPYTER_SHOW_FIRST = "nb_manager = chaste.visualization.JupyterNotebookManager()" +JUPYTER_SHOW = "nb_manager.vtk_show(scene, height=600)" + + +def clean_end_comment(_stripped_line): + """ + Remove the closing characters of a comment block from the stripped line. + + :param _stripped_line: The line to be cleaned. + :return: The cleaned line with the comment block end removed, if present. + """ + return _stripped_line + + +def ends_comment(_stripped_line): + """ + Determine if the stripped line is the end of a comment block. + + :param _stripped_line: The line to be checked. + :return: True if the line ends with '*/' or is '/', indicating the end of a comment block, False otherwise. + """ + return not _stripped_line.startswith("#") + + +def is_still_comment(_stripped_line): + """ + Check if the stripped line is part of a comment block. + + :param _stripped_line: The line to be checked. + :return: True if the line starts with '*', indicating it's part of a comment block, False otherwise. + """ + return _stripped_line.startswith("#") + + +def starts_comment(_stripped_line): + """ + Determine if the stripped line is the start of a comment block. + + :param _stripped_line: The line to be checked. + :return: True if the line starts with '/*' but not with '/**' (ignoring Doxygen comments), False otherwise. + """ + return _stripped_line.startswith("##") + + +def format_code_lines(code_lines: List[str]) -> str: + """ + Formats a list of code lines into a Markdown code block with Python syntax highlighting. + + :param code_lines: A list of strings, where each string is a line of code. + :return: A string representing the entire code block, enclosed within Markdown code block + fencing for Python syntax highlighting. + """ + joined_code = "\n".join(code_lines) + return f"```python\n{joined_code}\n```\n" + + +def get_last_revision_hash(abs_file_path: Path) -> str: + """ + Retrieves the hash of the last revision in which the specified file was changed. + + :param abs_file_path: An absolute path to the file within the Git repository. + :return: The hash of the last commit that changed the file. + """ + try: + rel_file_path = abs_file_path.relative_to(CHASTE_SRC_DIR) + rev_hash = check_output( + [ + "git", + "-C", + str(CHASTE_SRC_DIR), + "log", + "-n", + "1", + "--pretty=format:%H", + "--", + str(rel_file_path), + ], + encoding="utf-8", + ).strip() + return rev_hash + + except CalledProcessError as e: + print(f"Error getting last revision for {abs_file_path}.") + print(f"Output:\n{e.output}") + print(f"Error:\n{e.stderr}") + exit(e.returncode) + + +def get_list_of_tutorial_files() -> List[Path]: + """ + Scans the PYCHASTE_SRC_DIR directory recursively to find all files with a name + matching 'TestPy*Tutorial.py', and returns a list of their resolved paths. + + :return: List of resolved Path objects of tutorial source files. + """ + return [file.resolve() for file in PYCHASTE_SRC_DIR.rglob("TestPy*Tutorial.py")] + + +def get_output_file_name(abs_file_path: Path) -> str: + """ + Extracts the tutorial name from the source file path e.g. TestPyCellSortingTutorial.py -> CellSorting. + + :param abs_file_path: The Path object of the tutorial source file. + :return: The tutorial name extracted from the source file path. + """ + return abs_file_path.stem[len("TestPy") : -(len("Tutorial"))] + + +def get_revision_string_from_file(abs_file_path: Path) -> str: + """ + Forms a string at the top of each file that looks like: + + This tutorial is automatically generated from {file_link} at revision {revision}. + Note that the code is given in full at the bottom of the page. + + :param abs_file_path: The Path object of the file. + :return: A string containing the text indicated above. + """ + relative_file_path = abs_file_path.relative_to(CHASTE_SRC_DIR) + file_link = f"[{abs_file_path.name}](https://github.com/Chaste/Chaste/blob/develop/{relative_file_path})" + + revision = get_last_revision_hash(abs_file_path) + revision_link = ( + f"[{revision[0:12]}](https://github.com/Chaste/Chaste/commit/{revision})" + ) + + return f"This tutorial is automatically generated from {file_link} at revision {revision_link}." + + +def get_title_from_file(abs_file_path: Path) -> str: + """ + Extracts the tutorial title from the source file path e.g. TestPyCellSortingTutorial.py -> Cell Sorting. + + :param abs_file_path: The Path object of the tutorial source file. + :return: The tutorial title extracted from the source file path. + """ + tutorial_name = get_output_file_name(abs_file_path) + + # Split CamelCase string into list of words + words = re.findall(r"(\d+[A-Za-z]?|[A-Z][a-z]*|[A-Z]+(?=[A-Z]|$))", tutorial_name) + + # Join words with space and return the result + return " ".join(words) + + +def convert_tutorial_file(abs_file_path: Path) -> Tuple[str, NotebookNode, List[str]]: + """ + Convert a single tutorial source file to markdown format. + + Notes: + 1. Starts converting everything after the first #define line + 2. Stops converting after the final #endif. It is assumed that there + will be a #if line immediately after the first #define, and a count is kept of + #if and #endif lines seen in order to allow matching #if/endif blocks within + the converted section. + 3. All C-style block comments '/*' to '*/' are converted to markdown. + 4. All other lines, including C++ style comments '//', are kept as code lines + 5. In C-block comments (ie markdown), whitespace is removed. Bulleted lists + will work but nested bulleted lists won't. + 6. To print an empty line in the markdown page, just leave a blank line between paragraphs + in the comment, as for markdown pages. The older style of writing EMPTYLINE in a + paragraph by itself also works, but is deprecated. + 7. Lines inside a block comment which start with a '*', i.e. + /* my comment is + * two lines long */ + are ok, the initial '*' is removed. + + :param abs_file_path: The Path object of the tutorial file. + :return: a string representing the tutorial markdown text, and a list of strings representing the full code of the tutorial. + """ + # "State machine" state variables + parsing = False + status = ST_NONE + in_list = False + ifdefs_seen = 0 + + # Whether ends_comment and starts_comment can apply to the same line + end_can_be_start = False + + code_block_opener = "\n```python\n" + + # Output + markdown = [] + + notebook = new_notebook() # Jupyter notebook + notebook["cells"] = [] + notebook_cells = [] # [string, is_code] + last_notebook_cell = "" + + code_store = [] # Code lines + + line_store = [] # Input lines + last_line = "" + + with abs_file_path.open("r", encoding="utf-8") as fileobj: + + for line in fileobj: + line = line.rstrip() # Note: also removes '\n' + line_store.append(line) + # Remove all whitespace and save to a new string. + # We don't remove the initial whitespace as it will be needed for code lines. + stripped_line = line.strip() + squashed_line = "".join(stripped_line.split()) + + # We stop processing input after an #endif matching the initial include guard + if squashed_line.startswith("#endif"): + assert ifdefs_seen > 0, "#endif seen before #if" + ifdefs_seen -= 1 + if ifdefs_seen == 0: + if status is ST_CODE: + # close code block + markdown.append("```\n\n") + notebook_cells.append([last_notebook_cell, True]) + last_notebook_cell = "" + parsing = False + + # If in Parsing mode + if parsing: + if status in [ST_TEXT, ST_HOWTO]: + # We are still in a comment line, so strip it + line = stripped_line + + # Check if the line is a new text line + comment_started = False + if starts_comment(stripped_line): + comment_started = True + # remove all whitespace and the '/*' + stripped_line = line = stripped_line[2:].strip() + # if the last line was code, close the output code block + if status is ST_CODE: + markdown.append("```\n") + notebook_cells.append([last_notebook_cell, True]) + last_notebook_cell = "" + # set the status as text + status = ST_TEXT + elif status in [ST_TEXT, ST_HOWTO] and is_still_comment(stripped_line): + # we are in a comment, so get rid of whitespace and the initial '*' + stripped_line = line = line[1:].strip() + elif status is ST_NONE and len(stripped_line) > 0: + # Line has content and isn't a comment => it's code + markdown.append(code_block_opener) + notebook_cells.append([last_notebook_cell, False]) + last_notebook_cell = "" + status = ST_CODE + + # Check if comment ends + if ends_comment(stripped_line) and ( + not comment_started or end_can_be_start + ): + # If it's not a Doxygen comment, switch state to unknown + if status in [ST_TEXT, ST_HOWTO]: + # get rid of whitespace and '*/' + stripped_line = line = clean_end_comment(stripped_line) + status = ST_NONE + + # Check for (and strip) HOWTO tagging + if status is ST_TEXT and stripped_line.startswith(HOWTO_TAG): + status = ST_HOWTO + if status is ST_HOWTO: + if not stripped_line: + status = ST_TEXT # Blank comment line ends tagging + else: + stripped_line = line = "" # Strip tag content + + if status is ST_TEXT and stripped_line and stripped_line[0] == "*": + # It's a list, so needs some indentation! + in_list = True + if status is ST_TEXT and in_list: + line = " " + line + if in_list and (len(stripped_line) == 0 or status is not ST_TEXT): + in_list = False + + # If the line is a comment just saying 'EMPTYLINE', we'll print a blank line + if stripped_line == "EMPTYLINE": + stripped_line = line = "" + # We print the line unless we'd get 2 empty lines + if len(stripped_line) > 0 or len(last_line) > 0: + markdown.append(line + "\n") + last_notebook_cell += line + "\n" + + # If the line is a code line we store it, + # unless there would be two consecutive empty lines + if status is ST_CODE: + if len(stripped_line) > 0 or len(code_store[-1].strip()) > 0: + code_store.append(line) + + # We start processing lines AFTER the first #define.. + if squashed_line.startswith("#define"): + parsing = True + if squashed_line.startswith("#if"): + ifdefs_seen += 1 + last_line = stripped_line + + if not markdown: + # It's probably not C++ or Python, so let's include it all just as raw 'code' + code_store = line_store + + # Assemble the notebook cells + for notebook_cell in notebook_cells: + if notebook_cell[1]: + # Convert the notebook cell code to a list for easier processing + cell_code_lines = notebook_cell[0].split("\n") + + output_lines = [] + skip_list = ["unittest", "__main__", "self.assert"] + + for code_line in cell_code_lines: + code_line = code_line.rstrip() + indentation = len(code_line) + code_line = code_line.lstrip() + indentation -= len(code_line) + + # Strip out lines related to unittest and __main__ + if any(text in code_line for text in skip_list): + continue + + # Strip out class definition lines such as + # `class TestPyCellSortingTutorial(AbstractCellBasedTestSuite):` + if code_line.startswith("class Test"): + continue + + # Strip out test function definition lines such as + # `def test_potts_monolayer_cell_sorting(self):` + if code_line.startswith("def test_") and indentation == 4: + continue + + # Replace notebook placeholder strings + if "JUPYTER_SETUP" in code_line: + code_line = JUPYTER_SETUP + + elif "JUPYTER_TEARDOWN" in code_line: + code_line = JUPYTER_TEARDOWN + + elif "JUPYTER_SHOW_FIRST" in code_line: + code_line = JUPYTER_SHOW_FIRST + + elif "JUPYTER_SHOW" in code_line: + code_line = JUPYTER_SHOW + + elif "cell_based.VtkSceneModifier_2()" in code_line: + code_line = code_line.replace( + "cell_based.VtkSceneModifier_2()", + "visualization.JupyterSceneModifier_2(nb_manager)", + ) + + elif "cell_based.VtkSceneModifier_3()" in code_line: + code_line = code_line.replace( + "cell_based.VtkSceneModifier_3()", + "visualization.JupyterSceneModifier_3(nb_manager)", + ) + + # Reduce indentation by 8: 4 for `class Test...` and 4 for `def test_...` + indentation = max(indentation - 8, 0) + output_lines.append(" " * indentation + code_line) + + # Reassemble the notebook cell into a string, stripping empty lines + if output_lines: + output_string = os.linesep.join( + [line for line in output_lines if line.strip()] + ) + if output_string: + notebook["cells"].append(new_code_cell(output_string)) + + else: + # Markdown cell + notebook["cells"].append(new_markdown_cell(notebook_cell[0])) + + return "".join(markdown), notebook, code_store + + +def write_tutorial(abs_file_path: Path) -> None: + """ + Convert a tutorial source file to a Hugo markdown file and a Jupyter notebook. + + :param abs_file_path: The Path object of the tutorial source file. + """ + # Convert the tutorial file to markdown and Jupyter notebook format + test_markdown, test_notebook, test_code = convert_tutorial_file(abs_file_path) + + # Process the markdown file + markdown = [] + + # Add the Hugo front matter + markdown.append("---") + markdown.append(f'title : "{get_title_from_file(abs_file_path)}"') + markdown.append('summary: ""') + markdown.append("draft: false") + markdown.append("images: []") + markdown.append("toc: true") + markdown.append('layout: "single"') + markdown.append("---") + + # Add the revision string + revision_string = get_revision_string_from_file(abs_file_path) + markdown.append(revision_string) + markdown.append("\n\nNote that the code is given in full at the bottom of the page.\n\n") + + # Add the tutorial content + markdown.append(test_markdown) + + # Append the full source code + markdown.append(f"\n\n## Full code\n\n{format_code_lines(test_code)}") + + markdown_string = "\n".join(markdown) + + # Postprocess to remove empty lines at the end of code blocks + markdown_string = markdown_string.replace("\n\n```\n", "\n```\n") + + # Postprocess to remove more than one empty line above a code block + markdown_string = markdown_string.replace("\n\n\n```python\n", "\n\n```python\n") + + # Postprocess to remove any cases of 3 blank lines + markdown_string = markdown_string.replace("\n\n\n\n", "\n\n") + + # Postprocess to remove any cases of 2 blank lines + markdown_string = markdown_string.replace("\n\n\n", "\n\n") + + tutorial_name = get_output_file_name(abs_file_path) + markdown_file_path = PYCHASTE_TUTORIAL_DIR / f"{tutorial_name}.md" + with open(markdown_file_path, "w", encoding="utf-8") as file: + file.write(markdown_string) + + # Process the Jupyter notebook file + test_notebook["cells"].insert(0, new_markdown_cell(revision_string)) + + notebook_file_path = PYCHASTE_TUTORIAL_DIR / f"{tutorial_name}.ipynb" + with open(notebook_file_path, "w", encoding="utf-8") as file: + nbformat.write(test_notebook, file) + + +if __name__ == "__main__": + PYCHASTE_TUTORIAL_DIR.mkdir(parents=True, exist_ok=True) + if any(PYCHASTE_TUTORIAL_DIR.iterdir()): + warnings.warn(f"{PYCHASTE_TUTORIAL_DIR} is not empty") + + for tutorial_file in get_list_of_tutorial_files(): + write_tutorial(tutorial_file)